Está en la página 1de 952

Traducido del inglés al español - www.onlinedoctranslator.

com
PUBLICADO POR
Prensa de Microsoft
Una división de Microsoft Corporation
One Microsoft Way
Redmond, Washington 98052-6399

Copyright © 2004 por Steven C. McConnell

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.

Datos de catalogación en publicación de la Biblioteca del Congreso


McConnell, Steve
Código completo / Steve McConnell.--2nd ed.
pags. cm.

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

Impreso y encuadernado en los Estados Unidos de América.

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.

Editores de Adquisiciones:Linda Engelman y Robin Van Steenburgh Redactor


del proyecto:devon musgrave Indexador:Bill myers

Editor de escritorio principal:Carlos Diltz

N.º de pieza del cuerpo X10-53130


A mi esposa, Ashlie, que no tiene mucho que ver con la programación de computadoras.

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,

todavía estoy aprendiendo cosas de este libro!”

—John Robbins,Depuración de aplicaciones para Microsoft .NET y Microsoft Windows

“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.”

— Michael Howard, Ingeniería de Seguridad, Microsoft Corporation; Coautor,Escribir código seguro

“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,

las pruebas, la integración y la naturaleza de la artesanía del software”.

—Grady Booch,Soluciones de objetos

“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.

Código completoes un título bastante pobre para una obra brillante”.


—Jeff Duntemann,Técnicas de PC

“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.”

—Tommy Usher,Diario de usuarios de C

“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 I Sentando las bases


1 Bienvenido a Construcción de Software. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .3
2 Metáforas para una mejor comprensión del desarrollo de software . . . . .9 Medir
3 dos veces, cortar una vez: requisitos previos aguas arriba. . . . . . . . . . . . . . . . . 23
4 Decisiones clave de construcción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

Parte II Creación de código de alta calidad


5 Diseño en la Construcción. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
6 Clases obreras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 rutinas
7 de alta calidad. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 Programación
8 defensiva. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 El proceso de
9 programación del pseudocódigo . . . . . . . . . . . . . . . . . . . . . . . . . 215

Parte III Variables


10 Problemas generales en el uso de variables. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
11 El poder de los nombres de variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
12 Tipos de datos fundamentales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291 Tipos
13 de datos inusuales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319

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

Parte V Mejoras de código


20 El panorama de la calidad del software. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 463
21 Construcción colaborativa. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 479 Pruebas de
22 desarrollador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 499
23 Depuración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 535
24 Refactorización. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 563
25 Estrategias de ajuste de código. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 587
26 Técnicas de sintonización de códigos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 609

Parte VI Consideraciones del sistema


27 Cómo afecta el tamaño del programa a la construcción. . . . . . . . . . . . . . . . . . . . . . . .
28 649 Gestión de la construcción. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 661
29 Integración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 689
30 Herramientas de programación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 709

Parte VIIArtesanía de software


31 Maquetación y Estilo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 729
32 Código de autodocumentación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
33 777 Carácter personal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 819
34 Temas en Artesanía de Software. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 837 Dónde
35 encontrar más información . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 855
Tabla de contenido
Prefacio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix
Agradecimientos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .xxvii Lista de
listas de control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxix Lista de
tablas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxxi Lista de
figuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxxii

Parte I Sentando las bases


1 Bienvenido a Construcción de Software. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .3
1.1 ¿Qué es la construcción de software?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 ¿Por qué es importante la construcción de software? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.3 Cómo leer este libro. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

2 Metáforas para una mejor comprensión del desarrollo de software. . . . .9


2.1 La importancia de las metáforas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2 Cómo usar metáforas de software. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3 Metáforas comunes de software. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

3 Medir dos veces, cortar una vez: requisitos previos aguas arriba. . . . . . . . . . . . . . . . . 23

3.1 Importancia de los requisitos previos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24


3.2 Determine el tipo de software en el que está trabajando. . . . . . . . . . . . . . . . . . . . . . . . 31
3.3 Prerrequisito de definición de problema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.4 Requisitos Prerrequisito. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.5 Prerrequisito de arquitectura. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.6 Cantidad de tiempo para dedicar a los requisitos previos de Upstream. . . . . . . . . . . . . . . . . . . . . . 55

4 Decisiones clave de construcción. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61


4.1 Elección del lenguaje de programación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.2 Convenciones de programación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.3 Su ubicación en la ola tecnológica. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.4 Selección de las principales prácticas de construcción. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69

¿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

Parte II Creación de código de alta calidad

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

6 Clases Obreras. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125


6.1 Fundamentos de clase: tipos de datos abstractos (ADT) . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6.2 Interfaces de buena clase. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
6.3 Problemas de diseño e implementación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
6.4 Razones para Crear una Clase. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
6.5 Problemas específicos del idioma. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
6.6 Más allá de las clases: paquetes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156

7 Rutinas de alta calidad. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161


7.1 Razones válidas para crear una rutina. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
7.2 Diseño a nivel de rutina. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
7.3 Buenos nombres de rutinas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
7.4 ¿Cuánto puede durar una rutina? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
7.5 Cómo utilizar los parámetros de rutina. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
7.6 Consideraciones especiales en el uso de funciones. . . . . . . . . . . . . . . . . . . . . . . . . . . 181
7.7 Macro Rutinas y Rutinas en línea. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182

8 Programación defensiva. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187


8.1 Proteger su programa de entradas no válidas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
8.2 Afirmaciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
8.3 Técnicas de manejo de errores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
8.4 Excepciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
8.5 Bloquee su programa para contener el daño causado por los errores. . . . . . . . . . 203
8.6 Ayudas para la depuración. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
8.7 Determinar cuánta programación defensiva dejar
Codigo de producción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
8.8 Estar a la defensiva sobre la programación defensiva. . . . . . . . . . . . . . . . . . . . . . . . . . 210
Tabla de contenido xi

9 El proceso de programación del pseudocódigo. . . . . . . . . . . . . . . . . . . . . . . . . 215


9.1 Resumen de pasos en la construcción de clases y rutinas. . . . . . . . . . . . . . . . . . . . . . . 216
9.2 Pseudocódigo para profesionales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
9.3 Construcción de rutinas usando el PPP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
9.4 Alternativas al PPP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232

Parte III Variables


10 Problemas generales en el uso de variables. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
10.1 Alfabetización de datos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
10.2 Facilitando las declaraciones de variables. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
10.3 Directrices para inicializar variables. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
10.4 Alcance. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
10.5 Persistencia. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
10.6 Tiempo de vinculación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
10.7 Relación entre tipos de datos y estructuras de control. . . . . . . . . . . . . . . . . 254
10.8 Uso de cada variable para exactamente un propósito. . . . . . . . . . . . . . . . . . . . . . . . . . . . 255

11 El poder de los nombres de variables. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259


11.1 Consideraciones en la elección de buenos nombres. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
11.2 Nombrar tipos específicos de datos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
11.3 El poder de las convenciones de nomenclatura. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
11.4 Convenciones de nombres informales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
11.5 Prefijos estandarizados. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
11.6 Creación de nombres cortos legibles. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282
11.7 Tipos de nombres a evitar. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285

12 Tipos de datos fundamentales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291


12.1 Números en General. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
12.2 Números enteros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
12.3 Números de punto flotante. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295
12.4 Caracteres y cadenas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297
12.5 Variables booleanas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
12.6 Tipos enumerados. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
12.7 Constantes con nombre. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
12.8 Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
12.9 Creación de sus propios tipos (aliasing de tipos) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
19670.fm Página iv jueves, 7 de abril de 2011 17:54

PUBLICADO POR
Prensa de Microsoft
Una división de Microsoft Corporation
One Microsoft Way
Redmond, Washington 98052-6399

Copyright © 2004 por Steven C. McConnell

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.

Datos de catalogación en publicación de la Biblioteca del Congreso


McConnell, Steve
Código completo / Steve McConnell.--2nd ed.
pags. cm.

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

Impreso y encuadernado en los Estados Unidos de América.

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.

Editores de Adquisiciones:Linda Engelman y Robin Van Steenburgh Redactor


del proyecto:devon musgrave Indexador:Bill myers

Editor de escritorio principal:Carlos Diltz

N.º de pieza del cuerpo X10-53130


Tabla de contenido XIII

19.3 Sentencias nulas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444


19.4 Domar anidamientos peligrosamente profundos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445
19.5 Una base de programación: programación estructurada . . . . . . . . . . . . . . . . . . . 454
19.6 Estructuras de control y complejidad. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456

Parte V Mejoras de código


20 El panorama de la calidad del software. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 463
20.1 Características de la Calidad del Software. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 463
20.2 Técnicas para mejorar la calidad del software. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466
20.3 Eficacia relativa de las técnicas de calidad. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 469
20.4 Cuándo hacer el control de calidad. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473
20.5 El Principio General de la Calidad del Software. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 474

21 Construcción colaborativa. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 479


21.1 Descripción general de las prácticas de desarrollo colaborativo. . . . . . . . . . . . . . . . . . . . . . . 480
21.2 Programación por parejas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 483
21.3 Inspecciones formales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 485
21.4 Otros tipos de prácticas de desarrollo colaborativo. . . . . . . . . . . . . . . . . . . . . 492

22 Pruebas de desarrollador. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 499


22.1 Papel de las pruebas de desarrollador en la calidad del software. . . . . . . . . . . . . . . . . . . . . . . . . . . 500
22.2 Enfoque recomendado para las pruebas de desarrollador. . . . . . . . . . . . . . . . . . . . . . . . . 503
22.3 Bolsa de trucos de prueba. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505
22.4 Errores típicos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 517
22.5 Herramientas de soporte de pruebas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 523

22.6 Mejorar sus pruebas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 528


22.7 Mantenimiento de registros de pruebas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 529

23 Depuración. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 535
23.1 Descripción general de los problemas de depuración. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 535

23.2 Detección de un defecto. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 540


23.3 Reparación de un defecto. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 550
23.4 Consideraciones psicológicas en la depuración. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 554
23.5 Herramientas de depuración: obvias y no tan obvias. . . . . . . . . . . . . . . . . . . . . . . . 556
xiv Tabla de contenido

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

25 Estrategias de ajuste de código. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 587


25.1 Descripción general del rendimiento. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 588
25.2 Introducción al ajuste de código. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 591
25.3 Tipos de grasa y melaza. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 597
25.4 Medición. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 603
25.5 Iteración. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 605
25.6 Resumen del enfoque de ajuste de código. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 606

26 Técnicas de ajuste de código. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 609


26.1 Lógica. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 610
26.2 Bucles. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 616
26.3 Transformaciones de datos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 624
26.4 Expresiones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 630
26.5 Rutinas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 639
26.6 Grabación en un lenguaje de bajo nivel. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 640
26.7 Cuanto más cambian las cosas, más permanecen igual . . . . . . . . . . . . . . . . . 643

Parte VI Consideraciones del sistema

27 Cómo afecta el tamaño del programa a la construcción. . . . . . . . . . . . . . . . . . . . . . . . 649

27.1 Comunicación y Tamaño. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 650


27.2 Gama de tamaños de proyectos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 651
27.3 Efecto del tamaño del proyecto sobre los errores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 651
27.4 Efecto del tamaño del proyecto sobre la productividad. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 653
27.5 Efecto del tamaño del proyecto en las actividades de desarrollo. . . . . . . . . . . . . . . . . . . . . . . . . 654
Tabla de contenido XV

28 Gerenciamiento de la Construcción. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 661


28.1 Fomento de la buena codificación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 662
28.2 Gestión de la configuración. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 664
28.3 Estimación de un cronograma de construcción. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 671
28.4 Medición. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 677
28.5 Tratar a los programadores como personas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 680
28.6 Administración de su Gerente. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 686

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

30 Herramientas de programación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 709


30.1 Herramientas de diseño. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 710
30.2 Herramientas de código fuente. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 710
30.3 Herramientas de código ejecutable. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 716
30.4 Entornos orientados a herramientas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 720
30.5 Creación de sus propias herramientas de programación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 721
30.6 Herramienta Fantasyland . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 722

Parte VIIArtesanía de software


31 Maquetación y Estilo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 729
31.1 Fundamentos del diseño. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 730
31.2 Técnicas de diseño. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 736
31.3 Estilos de diseño. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 738
31.4 Diseño de estructuras de control. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 745
31.5 Disposición de extractos individuales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 753
31.6 Disposición de comentarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 763
31.7 Diseño de rutinas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 766
31.8 Diseño de clases. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 768
xvi Tabla de contenido

32 Código de autodocumentación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 777


32.1 Documentación externa. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 777
32.2 Estilo de programación como documentación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 778
32.3 Comentar o no comentar. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 781
32.4 Claves para comentarios efectivos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 785
32.5 Técnicas de comentarios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 792
32.6 Normas IEEE. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 813

33 Carácter personal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 819


33.1 ¿No está el carácter personal fuera del tema? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 820
33.2 Inteligencia y Humildad. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 821
33.3 Curiosidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 822
33.4 Honestidad Intelectual. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 826
33.5 Comunicación y Cooperación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 828
33.6 Creatividad y Disciplina. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 829
33.7 Pereza. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 830
33.8 Características que no importan tanto como podría pensar . . . . . . . . . . . 830
33.9 Hábitos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 833

34 Temas en Artesanía de Software. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 837


34.1 Conquistar la complejidad. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 837
34.2 Elija su proceso. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 839
34.3 Escribir programas para personas primero, computadoras segundo. . . . . . . . . . . . . . . . . . . . . 841
34.4 Programe en su idioma, no en él . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 843
34.5 Enfoca tu atención con la ayuda de convenciones. . . . . . . . . . . . . . . . . . . . . . 844
34.6 Programa en términos del dominio del problema. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 845
34.7 Esté atento a la caída de rocas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 848
34.8 Iterar, repetidamente, una y otra vez . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 850
34.9 Separarás el software y la religión . . . . . . . . . . . . . . . . . . . . . . . . 851
Tabla de contenido xvii

35 Dónde encontrar más información. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 855


35.1 Información sobre la construcción del software. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 856
35.2 Temas más allá de la construcción. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 857
35.3 Publicaciones periódicas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 859
35.4 Plan de lectura de un desarrollador de software. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 860
35.5 Unirse a una Organización Profesional. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 862

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

La brecha entre la mejor práctica de ingeniería de software y la práctica promedio es muy


amplia, quizás más amplia que en cualquier otra disciplina de ingeniería. Sería importante
una herramienta que difunda las buenas prácticas.
— Fred Brooks

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.

Aunque la práctica de desarrollo de software de vanguardia ha avanzado rápidamente en los últimos


años, la práctica común no lo ha hecho. Muchos programas todavía tienen errores, están retrasados
y superan el presupuesto, y muchos no logran satisfacer las necesidades de sus usuarios. Los
investigadores tanto en la industria del software como en entornos académicos han descubierto
prácticas efectivas que eliminan la mayoría de los problemas de programación que prevalecen desde
la década de 1970. Sin embargo, debido a que estas prácticas no suelen informarse fuera de las
páginas de revistas técnicas altamente especializadas, la mayoría de las organizaciones de
programación aún no las utilizan en la actualidad. Los estudios han encontrado que normalmente
toma de 5 a 15 años o más para que un desarrollo de investigación llegue a la práctica comercial
(Raghavan y Chand 1989, Rogers 1995, Parnas 1999). Este manual abrevia el proceso,

¿Quién debería leer este libro?


La experiencia en investigación y programación recopilada en este manual lo ayudará a crear
software de mayor calidad y a hacer su trabajo más rápido y con menos problemas. Este libro
le dará una idea de por qué ha tenido problemas en el pasado y le mostrará cómo evitar
problemas en el futuro. Las prácticas de programación que se describen aquí lo ayudarán a
mantener bajo control los grandes proyectos y a mantener y modificar el software con éxito a
medida que cambien las demandas de sus proyectos.

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

El contrapunto al programador con experiencia pero poca formación formal es el recién


graduado universitario. El recién graduado a menudo es rico en conocimientos teóricos
pero pobre en conocimientos prácticos que se utilizan para desarrollar programas de
producción. La tradición práctica de una buena codificación a menudo se transmite
lentamente en las danzas tribales rituales de los arquitectos de software, líderes de
proyectos, analistas y programadores más experimentados. Aún más a menudo, es el
producto de las pruebas y errores del programador individual. Este libro es una
alternativa al lento funcionamiento del potlatch intelectual tradicional. Reúne los consejos
útiles y las estrategias de desarrollo efectivas que antes estaban disponibles
principalmente mediante la caza y la recolección a partir de la experiencia de otras
personas.

¿Dónde más puede encontrar esta información?


Este libro sintetiza técnicas de construcción de una variedad de fuentes. Además de estar muy
dispersa, gran parte de la sabiduría acumulada sobre la construcción ha permanecido fuera de
las fuentes escritas durante años (Hildebrand 1989, McConnell 1997a). No hay nada misterioso
en las técnicas de programación eficaces y de gran potencia que utilizan los programadores
expertos. Sin embargo, en el ajetreo diario de desarrollar el último proyecto, pocos expertos
se toman el tiempo para compartir lo que han aprendido. Conse-
Prefacio xxx

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.

La información de este libro proviene de muchas fuentes, como se muestra a continuación. La


única otra forma de obtener la información que encontrará en este manual sería hojear una
montaña de libros y algunos cientos de revistas técnicas y luego agregar una cantidad
significativa de experiencia del mundo real. Si ya ha hecho todo eso, aún puede beneficiarse de
la recopilación de información de este libro en un solo lugar para una fácil referencia.

Profesional
experiencia
Otro software
libros
Programación
libros de idiomas
Construcción
Revista
Tecnología artículos
referencias

Beneficios clave de este manual


Cualquiera que sea su experiencia, este manual puede ayudarlo a escribir mejores programas en menos
tiempo y con menos dolores de cabeza.

Referencia completa de construcción de softwareEste manual analiza aspectos generales de la


construcción, como la calidad del software y formas de pensar acerca de la programación. Entra en
detalles esenciales de la construcción, como los pasos en la creación de clases, los entresijos del uso
de estructuras de datos y control, técnicas y estrategias de depuración, refactorización y ajuste de
código. No necesita leerlo de cabo a rabo para aprender sobre estos temas. El libro está diseñado
para facilitar la búsqueda de la información específica que le interesa.
XXII Prefacio

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.

Perspectiva más amplia sobre el desarrollo de softwareEste libro le dará la oportunidad de


superar la refriega de la lucha contra incendios del día a día y descubrir qué funciona y qué no.
Pocos programadores practicantes tienen tiempo para leer los cientos de libros y artículos de
revistas que se han destilado en este manual. La investigación y la experiencia del mundo real
reunidas en este manual informarán y estimularán su pensamiento sobre sus proyectos, lo que
le permitirá tomar medidas estratégicas para que no tenga que pelear las mismas batallas una
y otra vez.

Ausencia de bomboAlgunos libros de software contienen 1 gramo de conocimiento envuelto en 10


gramos de exageración. Este libro presenta discusiones equilibradas de las fortalezas y debilidades
de cada técnica. Usted conoce las demandas de su proyecto en particular mejor que nadie. Este libro
proporciona la información objetiva que necesita para tomar buenas decisiones sobre sus
circunstancias específicas.

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.

Acceso a otras fuentes de informaciónEste libro recopila gran parte de la información


disponible sobre la construcción de software, pero no es la última palabra. Durante el
Prefacio XXIII

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.

Por qué se escribió este manual


La necesidad de manuales de desarrollo que capturen el conocimiento sobre prácticas de
desarrollo efectivas es bien reconocida en la comunidad de ingeniería de software. Un informe
de la Junta de Tecnología y Ciencias de la Computación indicó que las mayores ganancias en la
calidad y productividad del desarrollo de software provendrán de la codificación, unificación y
distribución del conocimiento existente sobre prácticas efectivas de desarrollo de software
(CSTB 1990, McConnell 1997a). La junta concluyó que la estrategia para difundir ese
conocimiento debe basarse en el concepto de manuales de ingeniería de software.

Se ha dejado de lado el tema de la construcción


En un momento, se pensó que el desarrollo y la codificación de software eran lo mismo. Pero a
medida que se han identificado distintas actividades en el ciclo de vida del desarrollo de software,
algunas de las mejores mentes en el campo han dedicado su tiempo a analizar y debatir métodos de
gestión de proyectos, requisitos, diseño y pruebas. La prisa por estudiar estas áreas recién
identificadas ha dejado a la construcción de código como el primo ignorante del desarrollo de
software.

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.

No hay ningún libro comparable disponible

A la luz de la importancia obvia de la construcción, estaba seguro cuando


concebí este libro que alguien más ya habría escrito un libro sobre prácticas
efectivas de construcción. La necesidad de un libro sobre cómo programar
efectivamente parecía obvia. Pero descubrí que solo se habían escrito unos
pocos libros sobre construcción y solo sobre partes del tema. Algunos habían
sido escritos 15 años o más antes y empleaban lenguajes relativamente
esotéricos como ALGOL, PL/I, Ratfor y Smalltalk. Algunos fueron escritos por
profesores que no estaban trabajando en código de producción. Los profesores
escribieron sobre técnicas que funcionaron para los proyectos de los
estudiantes, pero a menudo tenían poca idea de cómo se desarrollarían las
técnicas en entornos de desarrollo a gran escala.

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.

Nota del autor


Agradezco sus consultas sobre los temas discutidos en este libro, sus informes de errores u
otros temas relacionados. Por favor contácteme enstevemcc@construx.com, o visite mi sitio
web enwww.stevemcconnell.com.

Bellevue, Washington
Día de los Caídos, 2004

Soporte técnico de aprendizaje de Microsoft


Se ha hecho todo lo posible para asegurar la exactitud de este libro. Microsoft Press
proporciona correcciones para libros a través de la World Wide Web en la siguiente
dirección:

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

Si tiene comentarios, preguntas o ideas sobre este libro, envíelos a


Microsoft Press mediante cualquiera de los siguientes métodos:

Apartado Postal:

Prensa de Microsoft

Atención: Code Complete 2E Editor


One Microsoft Way
Redmond, WA 98052-6399

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.

Un agradecimiento especial a los revisores de Construx Software que inspeccionaron formalmente


todo el manuscrito: Jason Hills, Bradey Honsinger, Abdul Nizar, Tom Reed y Pamela Perrott. Realmente
me sorprendió lo minuciosa que fue su revisión, especialmente considerando cuántos ojos habían
examinado el libro antes de comenzar a trabajar en él. Gracias también a Bradey, Jason y Pamela por
sus contribuciones alcc2e.comsitio web.

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

Casos de prueba 532

Recordatorios de depuración 559


Razones para refactorizar 570

Resumen de refactorizaciones 577


Refactorizar de forma segura 584

Estrategias de ajuste de código 607


Técnicas de ajuste de código 642

xxix
xxx listas de control

Integración de gestión de 669


configuración 707

Herramientas de programación 724

Diseño 773
Código de autodocumentación

780 Buena técnica de comentarios 816


Traducido del inglés al español - www.onlinedoctranslator.com

Mesas

Tabla 3-1 Costo promedio de reparar defectos en función de cuándo se presentan y


detectan 29

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

Tabla 5-1 diseño populares 104

Tabla 5-2 Formalidad del diseño y nivel de detalle Variaciones 116


Tabla 6-1 necesarias en las rutinas heredadas 145 Compatibilidad

Tabla 8-1 con lenguaje popular para excepciones Ejemplos de 198


Tabla 11-1 nombres de variables buenos y malos 261
Tabla 11-2 Nombres de variables que son demasiado largos, demasiado cortos o correctos 262
Tabla 11-3 Ejemplos de convenciones de nomenclatura para C++ y Java 277
Tabla 11-4 Convenciones de nomenclatura de muestra para C 278

Tabla 11-5 Convenciones de nomenclatura de muestra para Visual Basic 278


Tabla 11-6 Muestra de UDT para un procesador de textos Prefijos 280
Tabla 11-7 semánticos 280

Tabla 12-1 Rangos para diferentes tipos de enteros 294


Tabla 13-1 Acceso a datos globales directamente y a través de rutinas de acceso 341
Tabla 13-2 Usos paralelos y no paralelos de datos complejos 342

Tabla 16-1 Los tipos de bucles 368


Tabla 19-1 Transformaciones de expresiones lógicas bajo los teoremas de DeMorgan 436
Tabla 19-2 Técnicas para contar los puntos de decisión en una rutina 458
Tabla 20-1 Clasificación del equipo en cada objetivo 469 Tasas

Tabla 20-2 de detección de defectos 470

Tabla 20-3 Tasa estimada de detección de defectos de programación extrema472

Tabla 21-1 Comparación de técnicas de construcción colaborativa Ejemplos495


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

Cuadro 25-2 comunes 601

xxi
xxxii Mesas

Tabla 27-1 652 del


Tamaño del proyecto y densidad típica de errores Tamaño

Tabla 27-2 proyecto y productividad 653 Factores que influyen en el

Cuadro 28-1 674


esfuerzo del proyecto de software Medidas útiles de desarrollo

Tabla 28-2 de software Una visión de cómo los programadores emplean678


su

Tabla 28-3 tiempo 681


Cifras
Figura 1-1 Las 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. 4

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-2 Es difícil extender adecuadamente la metáfora de la agricultura al


desarrollo de software. 15

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-5 Asegúrate de saber a qué apuntas antes de disparar. 38


Figura 3-6 Sin buenos requisitos, puede tener el problema general correcto pero no dar en
el blanco en aspectos específicos del problema. 39

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

Figura 5-1 El puente de Tacoma Narrows: un ejemplo de un problema perverso. 75

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-3 Un ejemplo de un sistema con seis subsistemas. 83

Figura 5-4 Un ejemplo de lo que sucede sin restricciones en las comunicaciones


entre subsistemas. 83

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

orden que se muestra aquí. 216

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 10-4 Los datos iterativos se repiten. 255

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

relacionadas no se superponen. Pueden estar anidados. 352

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

para atacar problemas adecuados. 394

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 18-5 El enfoque de escalones clasifica cada entrada determinando el nivel en el


que llega a una "escalera". El "paso" que golpea determina su categoría. 426

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 20-2 Ni el enfoque de desarrollo más rápido ni el más lento produce el


software con más defectos. 475
Figura 22-1 A 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”. 502

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-1 El número de vías de comunicación aumenta proporcionalmente al cuadrado


del número de personas en el equipo. 650

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-1 El 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. 690

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:

Capítulo 1: Bienvenido a la construcción de software. . . . . . . . . . . . . . . . . . . . . . . 3


Capítulo 2: Metáforas para una mejor comprensión de
Desarrollo de software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba . . . . . . . .

.23 Capítulo 4: Decisiones clave de construcción . . . . . . . . . . . . . . . . . . . . . . . . . . . . .61


Capítulo 1

Bienvenido a Software
Construcción
cc2e.com/0178 Contenido

- 1.1 ¿Qué es la construcción de software?: página 3

- 1.2 ¿Por qué es importante la construcción de software?: página 6

- 1.3 Cómo leer este libro: página 8

Temas relacionados

- Quién debería leer este libro: Prefacio

- Beneficios de leer el libro: Prefacio

- Por qué se escribió el libro: Prefacio

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.

1.1 ¿Qué es la construcción de software?


El desarrollo de software de computadora puede ser un proceso complicado, y en los últimos 25 años, los
investigadores han identificado numerosas actividades distintas que intervienen en el desarrollo de
software. Incluyen

- Definición del problema

- Desarrollo de requisitos
- Planificación de la construcción

- Arquitectura de software o diseño de alto nivel

- 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

- Pruebas del sistema

- 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.

Si ha aprendido a programar por sí mismo o ha trabajado principalmente en proyectos informales, es


posible que no haya hecho distinciones entre las muchas actividades que intervienen en la creación de un
producto de software. Mentalmente, es posible que haya agrupado todas estas actividades como
"programación". Si trabaja en proyectos informales, la actividad principal en la que piensa cuando piensa en
crear software es probablemente la actividad a la que los investigadores se refieren como "construcción".

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.

La construcción también se conoce a veces como "codificación" o "programación".


“Codificación” no es realmente la mejor palabra porque implica la traducción mecánica de un
diseño preexistente a un lenguaje informático; la construcción no es en absoluto mecánica e
implica creatividad y juicio sustanciales. A lo largo del libro, uso "programación" de manera
intercambiable con "construcción".

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

Requisitos Diseño Mantenimiento

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

- Determinar cómo se probará su código


6 Capítulo 1: Bienvenido a la construcción de software

- Diseño y redacción de clases y rutinas.


- Crear y nombrar variables y constantes con nombre

- Selección de estructuras de control y organización de bloques de sentencias

- Pruebas unitarias, pruebas de integración y depuración de su propio código

- Revisar los diseños y el código de bajo nivel de otros miembros del equipo y pedirles que
revisen el suyo

- Pulir el código formateándolo y comentándolo con cuidado

- Integración de componentes de software que se crearon por separado

- Tuning código para hacerlo más rápido y usar menos recursos

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.

1.2 ¿Por qué es importante la construcción de software?


Como está leyendo este libro, probablemente esté de acuerdo en que es importante mejorar la calidad del software
y la productividad del desarrollador. Muchos de los proyectos más emocionantes de la actualidad utilizan software
de forma extensiva. Internet, los efectos especiales de las películas, los sistemas médicos de soporte vital, los
programas espaciales, la aeronáutica, el análisis financiero de alta velocidad y la investigación científica son algunos
ejemplos. Estos proyectos y los proyectos más convencionales pueden beneficiarse de prácticas mejoradas porque
muchos de los fundamentos son los mismos.

Si está de acuerdo en que mejorar el desarrollo de software es importante en general, la pregunta


para usted como lector de este libro es: ¿Por qué la construcción es un enfoque importante?
1.2 ¿Por qué es importante la construcción de software? 7

Este es el por qué:

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

de tiempo consumido por la


proyecto. Cualquier cosa que ocupe tanto tiempo del proyecto está destinada a afectar el éxito del
construcción, consulte "Proporciones y proyecto.
tamaño de la actividad" en la Sección

27.5. La construcción es la actividad central en el desarrollo de software.Los requisitos y la arquitectura se


realizan antes de la construcción para que pueda realizar la construcción de manera efectiva. La prueba del
sistema (en el sentido estricto de prueba independiente) se realiza después de la construcción para verificar
que la construcción se haya realizado correctamente. La construcción está en el centro del proceso de
desarrollo de software.

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

individual" en la Sección 28.5.


productividad de los programadores individuales variaba en un factor de 10 a 20 durante la
construcción (1968). Desde su estudio, sus resultados han sido confirmados por muchos otros
estudios (Curtis 1981, Mills 1983, Curtis et al. 1986, Card 1987, Valett y McGarry 1989, DeMarco
y Lister 1999, Boehm et al. 2000). Este libro ayuda a todos los programadores a aprender
técnicas que ya utilizan los mejores programadores.

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

1.3 Cómo leer este libro


Este libro está diseñado para ser leído de cabo a rabo o por tema. Si le gusta leer libros de principio a
fin, simplemente puede sumergirse en el Capítulo 2, "Metáforas para una mejor comprensión del
desarrollo de software". Si desea obtener consejos de programación específicos, puede comenzar
con el Capítulo 6, "Clases trabajadoras", y luego seguir las referencias cruzadas a otros temas que
encuentre interesantes. Si no está seguro de si algo de esto se aplica a usted, comience con la
Sección 3.2, "Determine el tipo de software en el que está trabajando".

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.

- Las principales actividades en la construcción son el diseño detallado, la codificación, la depuración, la


integración y las pruebas de desarrollador (pruebas unitarias y pruebas de integración).

- Otros términos comunes para la construcción son "codificación" y "programación".

- La calidad de la construcción afecta sustancialmente la calidad del software.

- En el análisis final, su comprensión de cómo construir determina qué tan


buen programador es, y ese es el tema del resto del libro.
Capitulo 2

Metáforas para una mejor


comprensión del desarrollo de
software
cc2e.com/0278 Contenido

- 2.1 La importancia de las metáforas: página 9

- 2.2 Cómo usar metáforas de software: página 11

- 2.3 Metáforas comunes de software: página 13

Tema relacionado

- Heurísticas en el diseño: “El diseño es un proceso heurístico” en la Sección 5.1

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?

Estas metáforas gráficas describen fenómenos de software específicos. Metáforas igualmente


vívidas describen fenómenos más amplios y puede utilizarlas para mejorar su comprensión del
proceso de desarrollo de software.

El resto del libro no depende directamente de la discusión de metáforas en este


capítulo. Sáltatelo si quieres llegar a las sugerencias prácticas. Léalo si quiere pensar
más claramente en el desarrollo de software.

2.1 La importancia de las metáforas


Desarrollos importantes a menudo surgen de analogías. Al comparar un tema que
entiende mal con algo similar que entiende mejor, puede obtener ideas que resulten
en una mejor comprensión del tema menos familiar. Este uso de la metáfora se
llama “modelado”.

La historia de la ciencia está llena de descubrimientos basados en explotar el poder de las


metáforas. El químico Kekulé tuvo un sueño en el que vio a una serpiente agarrarse la cola con
la boca. Cuando despertó, se dio cuenta de que una estructura molecular basada en una
forma de anillo similar explicaría las propiedades del benceno. La experimentación adicional
confirmó la hipótesis (Barbour 1966).
9
10 Capítulo 2: Metáforas para una mejor comprensión del desarrollo de software

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

comportamiento esperado que


en 1973, el procesamiento de datos estaba cambiando de una visión de los sistemas de información
todos comprenden. Se reducen las centrada en la computadora a una visión centrada en la base de datos. Bachman señaló que los antiguos
comunicaciones innecesarias y los
del procesamiento de datos querían ver todos los datos como un flujo secuencial de tarjetas que fluyen a
malentendidos. El aprendizaje y la
través de una computadora (la visión centrada en la computadora). El cambio consistía en centrarse en un
educación son más rápidos. En

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.

2.2 Cómo usar metáforas de software


Una metáfora del software se parece más a un reflector que a un mapa de carreteras. No te
dice dónde encontrar la respuesta; te dice como buscarlo. Una metáfora sirve más como
heurística que como algoritmo.
PUNTO CLAVE

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,

consulte "El diseño es un proceso


está nuestra casa. Todo el mundo nos conoce, alguien estará encantado de ayudarle. Si no encuentras a
heurístico" en la Sección 5.1. nadie, llámanos desde un teléfono público e iremos a buscarte.

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

2.3 Metáforas de software comunes


Ha surgido una confusa abundancia de metáforas en torno al desarrollo de software. David
Gries dice que escribir software es una ciencia (1981). Donald Knuth dice que es un arte (1998).
Watts Humphrey dice que es un proceso (1989). PJ Plauger y Kent Beck dicen que es como
conducir un automóvil, aunque llegan a conclusiones casi opuestas (Plauger 1993, Beck 2000).
Alistair Cockburn dice que es un juego (2002). Eric Raymond dice que es como un bazar (2000).
Andy Hunt y Dave Thomas dicen que es como la jardinería. Paul Heckel dice que es como
filmarBlanca Nieves y los Siete Enanos(1994). Fred Brooks dice que es como cultivar, cazar
hombres lobo o ahogarse con dinosaurios en un pozo de alquitrán (1995). ¿Cuáles son las
mejores metáforas?

Caligrafía de software: escribir código


La metáfora más primitiva para el desarrollo de software surge de la expresión "escribir
código". La metáfora de la escritura sugiere que desarrollar un programa es como escribir una
carta informal: te sientas con pluma, tinta y papel y la escribes de principio a fin. No requiere
ninguna planificación formal, y usted descubre lo que quiere decir sobre la marcha.

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.

Cultivo de software: crecimiento de un sistema

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

análisis y diseño de sistemas(


plan del sistema, simplificar el diseño detallado, aumentar los rendimientos del código a través
Weinberg 1988). de una gestión eficaz de la tierra y cosechar el código en sí. hablarás de
2.3 Metáforas de software comunes 15

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.

La debilidad en la metáfora de la agricultura de software es su sugerencia de que no tiene ningún


control directo sobre cómo se desarrolla el software. Plantas las semillas del código en la primavera.
Almanaque del granjeroy si la Gran Calabaza lo permite, tendrá una excelente cosecha de código en
el otoño.

Figura 2-2Es difícil extender adecuadamente la metáfora de la agricultura al desarrollo


de software.

Software Oyster Farming: Acrecentamiento del sistema

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,

consulte la Sección 29.2, “Frecuencia de


vez. Otras palabras estrechamente relacionadas con la acumulación son "incremental", "iterativa",
integración: ¿por fases o incremental?” "adaptativa" y "evolutiva". El diseño, la construcción y las pruebas incrementales son algunos de los
conceptos de desarrollo de software más poderosos disponibles.

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.

La evidencia anecdótica a favor de este enfoque es impresionante. Fred Brooks, quien en


1975 aconsejó construir uno para tirar, dijo que nada en la década posterior a la
publicación de su libro históricoEl Hombre-Mes Míticocambió tan radicalmente su propia
práctica o su eficacia como desarrollo incremental (1995). Tom Gilb señaló lo mismo en
su innovador libro,Principios de Gestión de Ingeniería de Software (1988), que introdujo
Evolutionary Delivery y sentó las bases para gran parte del enfoque de programación
Agile actual. Numerosas metodologías actuales se basan en esta idea (Beck 2000,
Cockburn 2002, Highsmith 2002, Reifer 2002, Martin 2003, Larman 2004).

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.

Construcción de software: software de construcción

La imagen de software de "construcción" es más útil que la de software de "escritura" o


"crecimiento". Es compatible con la idea de acumulación de software y proporciona una guía
más detallada. La creación de software implica varias etapas de planificación, preparación y
PUNTO CLAVE
ejecución que varían en tipo y grado según lo que se esté construyendo. Cuando exploras la
metáfora, encuentras muchos otros paralelos.

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.

Mayor complejidad y tamaño implican mayores consecuencias en ambas actividades. En la


construcción de una casa, los materiales son algo caros, pero el principal gasto es la mano de obra.
Arrancar una pared y moverla seis pulgadas es costoso, no porque desperdicies muchos clavos, sino
porque tienes que pagarle a la gente por el tiempo extra que lleva mover la pared. Tiene que hacer
que el diseño sea lo mejor posible, como se sugiere en la Figura 2-4, para no perder el tiempo
corrigiendo errores que podrían haberse evitado. Al construir un producto de software, los
materiales son incluso menos costosos, pero la mano de obra cuesta lo mismo. Cambiar el formato
de un informe es tan costoso como mover una pared de una casa porque el principal componente
del costo en ambos casos es el tiempo de las personas.
18 Capítulo 2: Metáforas para una mejor comprensión del desarrollo 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.

Una planificación cuidadosa no significa necesariamente una planificación exhaustiva o una


planificación excesiva. Puede planificar los soportes estructurales y decidir más tarde si colocar pisos
de madera o alfombras, de qué color pintar las paredes, qué material de techo usar, etc.
2.3 Metáforas de software comunes 19

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.

Finalmente, la analogía de la construcción proporciona información sobre proyectos de software extremadamente


grandes. Debido a que la penalización por falla en una estructura extremadamente grande es severa, la estructura
debe tener un exceso de ingeniería. Los constructores hacen e inspeccionan sus planos cuidadosamente.
Construyen márgenes de seguridad; es mejor pagar un 10 por ciento más por un material más resistente que que se
derrumbe un rascacielos. Se presta mucha atención al tiempo. Cuando se construyó el Empire State Building, cada
camión de reparto tenía un margen de 15 minutos para realizar su entrega. Si un camión no estaba en el lugar en el
momento adecuado, todo el proyecto se retrasaba.

Asimismo, para proyectos de software extremadamente grandes, se necesita una planificación


de mayor orden que para proyectos que son simplemente grandes. Capers Jones informa que
un sistema de software con un millón de líneas de código requiere un promedio de 69tiposde
documentación (1998). La especificación de requisitos para un sistema de este tipo suele tener
entre 4000 y 5000 páginas, y la documentación de diseño puede ser dos o tres veces más
extensa que los requisitos. Es poco probable que una persona pueda comprender el diseño
completo de un proyecto de este tamaño, o incluso leerlo. Un mayor grado de preparación es
apropiado.

Construimos proyectos de software comparables en tamaño económico al Empire State Building, y


se necesitan controles técnicos y administrativos de estatura similar.

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

Aplicación de técnicas de software: la caja de herramientas intelectual

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

5.3, “Bloques de construcción del diseño:


metodología al 100 por ciento, verá el mundo entero en términos de esa metodología. En
heurística”. algunos casos, perderá oportunidades de utilizar otros métodos más adecuados para su
problema actual. La metáfora de la caja de herramientas ayuda a mantener todos los métodos,
técnicas y consejos en perspectiva, listos para usar cuando sea apropiado.

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.

Floyd, Robert W. "Los paradigmas de la programación". 1978 Conferencia Premio Turing.


Comunicaciones de la ACM, agosto de 1979, págs. 455–60. Esta es una discusión fascinante
sobre modelos en el desarrollo de software, y Floyd aplica las ideas de Kuhn al tema.
Puntos clave 21

Puntos clave
- Las metáforas son heurísticas, no algoritmos. Como tal, tienden a ser un poco descuidados.

- Las metáforas lo ayudan a comprender el proceso de desarrollo de software


relacionándolo con otras actividades que ya conoce.

- Algunas metáforas son mejores que otras.

- Tratar la construcción de software como similar a la construcción de edificios sugiere que se


necesita una preparación cuidadosa e ilumina la diferencia entre proyectos grandes y
pequeños.

- 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.

- Las metáforas no son mutuamente excluyentes. Usa la combinación de metáforas que


funcione mejor para ti.
Capítulo 3

Medir dos veces, cortar una vez:


requisitos previos aguas arriba

cc2e.com/0309 Contenido

- 3.1 Importancia de los requisitos previos: página 24

- 3.2 Determine el tipo de software en el que está trabajando: página 31

- 3.3 Prerrequisito de la definición del problema: página 36

- 3.4 Requisitos Requisito previo: página 38

- 3.5 Arquitectura Prerrequisito: página 43

- 3.6 Cantidad de tiempo para dedicar a los requisitos previos de Upstream: página 55

Temas relacionados

- Decisiones clave de construcción: Capítulo 4

- Efecto del tamaño del proyecto en la construcción y requisitos previos: Capítulo 27

- Relación entre objetivos de calidad y actividades de construcción: Capítulo 20

- Gestión de la construcción: Capítulo 28

- 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.

3.1 Importancia de los requisitos previos


Referencia cruzadaPago Un denominador común de los programadores que crean software de alta calidad es el uso de
la atención a la calidad es también la mejor
prácticas de alta calidad. Tales prácticas enfatizan la calidad al principio, a la mitad y al final de
manera de mejorar la productividad. Para

obtener más información, consulte la


un proyecto.
Sección 20.5, “El principio general del

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.

Si enfatiza la calidad en medio del proyecto, enfatiza las prácticas de construcción.


Tales prácticas son el foco de la mayor parte de este libro.

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.

Dado que la construcción se encuentra en medio de un proyecto de software, cuando llega a la


construcción, las partes anteriores del proyecto ya han sentado algunas de las bases para el
éxito o el fracaso. Sin embargo, durante la construcción, al menos debería poder determinar
qué tan buena es su situación y retroceder si ve nubes negras de fallas que se avecinan en el
horizonte. El resto de este capítulo describe en detalle por qué es importante una preparación
adecuada y le dice cómo determinar si está realmente listo para comenzar la construcción.
3.1 Importancia de los requisitos previos 25

¿Se aplican los requisitos previos a los proyectos de software modernos?

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

construcción comience en serio.

El objetivo general de la preparación es la reducción de riesgos: un buen planificador de proyectos elimina


los principales riesgos lo antes posible para que la mayor parte del proyecto pueda proceder de la mejor
manera posible. Con mucho, los riesgos de proyecto más comunes en el desarrollo de software son los
PUNTO CLAVE
requisitos deficientes y la planificación deficiente del proyecto, por lo que la preparación tiende a centrarse
en mejorar los requisitos y los planes del proyecto.

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.

Causas de una preparación incompleta


Se podría pensar que todos los programadores profesionales conocen la importancia de la
preparación y comprueban que se han cumplido los requisitos previos antes de saltar a la
construcción. Desafortunadamente, eso no es así.

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

Suponga que ya ha estado en la montaña de la definición del problema, ha caminado una


milla con el hombre de los requisitos, se ha quitado la ropa sucia en la fuente de la
arquitectura y se ha bañado en las aguas puras de la preparación. Entonces sabe que antes de
implementar un sistema, necesita comprender qué se supone que debe hacer el sistema y
cómo se supone que debe hacerlo.

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.

También es importante pensar en cómo construir el sistema antes de empezar a construirlo.


No desea gastar mucho tiempo y dinero en callejones sin salida cuando no es necesario,
especialmente cuando eso aumenta los costos.

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

No empiezas a decorar el árbol de Navidad hasta que lo pones en el soporte. No enciendes un


fuego hasta que hayas abierto la chimenea. No haces un viaje largo con el tanque de gasolina
vacío. No te vistes antes de ducharte, y no te pones los zapatos antes que los calcetines.
También debe hacer las cosas en el orden correcto en el software.

Los programadores están al final de la cadena alimenticia del software. El arquitecto


consume los requisitos; el diseñador consume la arquitectura; y el codificador consume
el diseño.

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.

Apelación a los datos

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

3 Investigadores de Hewlett-Packard, IBM, Hughes Aircraft, TRW y otras organizaciones han


2
1 descubierto que eliminar un error al comienzo de la construcción permite volver a realizar el
trabajo de 10 a 100 veces menos costoso que cuando se realiza en la última parte del proceso.
DATOS DUROS
durante la prueba del sistema o después de la liberación (Fagan 1976; Humphrey, Snyder y
Willis 1991; Leffingwell 1997; Willis et al. 1998; Grady 1999; Shull et al. 2002; Boehm y Turner
2004).

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

Requisitos 1 3 5–10 10 10–100


Arquitectura — 1 10 15 25–100
Construcción — — 1 10 10–25

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

Requisitos Construcción Posteriores a la liberación


Arquitectura Prueba del sistema

Fase en la que se detecta un defecto

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.

Prueba de preparación para jefes

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.

¿Cuáles de estas declaraciones son profecías autocumplidas?

- 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

con el que nos encontremos durante la codificación o la depuración.

Todas estas declaraciones son profecías autocumplidas. Apunta al último.

Si aún no está convencido de que los requisitos previos se aplican a su proyecto, la siguiente sección
lo ayudará a decidir.

3.2 Determine el tipo de software en el que está trabajando


Capers Jones, científico jefe de Software Productivity Research, resumió 20 años de
investigación de software señalando que él y sus colegas han visto 40 métodos
diferentes para recopilar requisitos, 50 variaciones en el trabajo de diseños de software y
30 tipos de pruebas aplicadas a proyectos en más de 700 lenguajes de programación
diferentes (Jones 2003).

Diferentes tipos de proyectos de software requieren diferentes equilibrios entre la preparación y la


construcción. Cada proyecto es único, pero los proyectos tienden a caer en estilos generales de
desarrollo. La Tabla 3-2 muestra tres de los tipos de proyectos más comunes y enumera las prácticas
que normalmente se adaptan mejor a cada tipo de proyecto.

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

Típico sitio de Internet software integrado software de aviónica


aplicaciones
sitio de intranet Juegos software integrado
Inventario sitio de Internet Dispositivos médicos
administración
Software empaquetado Sistemas operativos
Juegos Herramientas de software Software empaquetado
administración
servicios web
sistemas de información

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

Planificación y proyecto incremental básico por adelantado Amplio por adelantado


administración planificación planificación planificación

Prueba según sea necesario y Planificación básica de pruebas Prueba extensa


planificación de control de calidad planificación
Control de calidad según sea necesario

cambio informal planificación Control de calidad extenso

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

Según sea necesario Requisitos formales


Comentarios de comentarios inspecciones
Diseño Diseño y codificación Diseño arquitectonico Diseño arquitectonico
están combinados
informal detallado arquitectura formal
diseño inspecciones

Diseño según sea necesario formal detallado


reseñas diseño
formal detallado
inspecciones de diseño

Construcción Programación en pareja Programación en pareja Programación en pareja o


o codificación individual o codificación individual codificación individual

Check-in informal Check-in informal registro formal


procedimiento o no procedimiento procedimiento
procedimiento de registro
Código según sea necesario código formal
reseñas inspecciones

Pruebas Prueba de desarrolladores Prueba de desarrolladores Los desarrolladores prueban su

y control de calidad su propio código su propio código código propio

Prueba primero Prueba primero Prueba primero

desarrollo desarrollo desarrollo


Pocas o ninguna prueba por Pruebas separadas Pruebas separadas
parte de un grupo de prueba grupo grupo
separado
Grupo de control de calidad separado

Despliegue Despliegue informal- Despliegue formal Despliegue formal


procedimiento de ment procedimiento procedimiento

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.

Efecto de los enfoques iterativos en los requisitos previos

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,

Tabla 3-3 Efecto de omitir requisitos previos en proyectos secuenciales e iterativos

Enfoque #1: Enfoque Enfoque #2: Iterativo


secuencial sin Acercamiento sin
requisitos previos requisitos previos

Finalización del proyecto Costo de Costo de


Estado Costo de trabajo Rehacer Costo de trabajo Rehacer

20% $100,000 $0 $100,000 $75,000


40% $100,000 $0 $100,000 $75,000
60% $100,000 $0 $100,000 $75,000
80% $100,000 $0 $100,000 $75,000
100% $100,000 $0 $100,000 $75,000
fin de proyecto
Rehacer $0 $500,000 $0 $0
TOTAL $500,000 $500,000 $500,000 $375,000
GRAN TOTAL $1,000,000 $875,000

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.

Tabla 3-4 Efecto de centrarse en los requisitos previos en proyectos secuenciales e


iterativos

Enfoque #3: Enfoque secuencial Enfoque #4: Iterativo


con requisitos previos Enfoque con requisitos previos
finalización del proyecto Costo de Costo de
estado Costo de trabajo Rehacer Costo de trabajo Rehacer
20% $100,000 $20,000 $100,000 $10,000
40% $100,000 $20,000 $100,000 $10,000
60% $100,000 $20,000 $100,000 $10,000
80% $100,000 $20,000 $100,000 $10,000
100% $100,000 $20,000 $100,000 $10,000
fin de proyecto
Rehacer $0 $0 $0 $0
TOTAL $500,000 $100,000 $500,000 $50,000
GRAN TOTAL $600,000 $ 550,000

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

tamaños, consulte el Capítulo 27, “Cómo el


practicar un control de cambios sistemático para aceptar solo los nuevos requisitos más valiosos a
tamaño del programa afecta la medida que avanza el proyecto. Otra alternativa es especificar solo el 20 por ciento más importante
construcción”.
de los requisitos por adelantado y planificar el desarrollo del resto del software en pequeños
incrementos, especificando requisitos y diseños adicionales sobre la marcha. Las figuras 3-2 y 3-3
reflejan estos diferentes enfoques.
3.2 Determine el tipo de software en el que está trabajando 35

Requisitos

Arquitectura

Diseño detallado

Construcción

Garantía de calidad/Pruebas del sistema

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

Garantía de calidad/Pruebas del sistema

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.

Elegir entre enfoques iterativos y secuenciales


La medida en que se deben cumplir los requisitos previos variará según el tipo de proyecto
indicado en la Tabla 3-2, la formalidad del proyecto, el entorno técnico, las capacidades del
personal y los objetivos comerciales del proyecto. Puede elegir un enfoque más secuencial (por
adelantado) cuando

- Los requisitos son bastante estables.

- El diseño es sencillo y bastante bien entendido.


- El equipo de desarrollo está familiarizado con el área de aplicaciones.
36 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba

- El proyecto contiene poco riesgo.

- La previsibilidad a largo plazo es importante.

- Es probable que el costo de cambiar los requisitos, el diseño y el código en sentido descendente sea
alto.

Puede elegir un enfoque más iterativo (sobre la marcha) cuando

- Los requisitos no se entienden bien o espera que sean inestables por otras
razones.
- El diseño es complejo, desafiante o ambos.

- El equipo de desarrollo no está familiarizado con el área de aplicaciones.

- El proyecto contiene mucho riesgo.

- La previsibilidad a largo plazo no es importante.

- 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".

3.3 Prerrequisito de definición del problema


Si la “caja” es el límite de las El primer requisito previo que debe cumplir antes de comenzar la construcción es una
restricciones y condiciones,
declaración clara del problema que se supone que debe resolver el sistema. Esto a veces se
entonces el truco es encontrar
la caja.... No pienses fuera de denomina "visión del producto", "declaración de la visión", "declaración de la misión" o
la caja, encuentra la caja. "definición del producto". Aquí se llama “definición del problema”. Dado que este libro trata
—Andy Hunt y Dave sobre la construcción, esta sección no le dice cómo escribir una definición de problema; te dice
Thomas
cómo reconocer si uno ha sido escrito y si el que está escrito formará una buena base para la
construcción.
3.3 Prerrequisito de definición del problema 37

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

Pruebas del sistema

Construcción

Arquitectura

Requisitos

Definición del problema

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

en términos informáticos o de programador.

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

Figura 3-5Asegúrate de saber a qué apuntas antes de disparar.

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

3.4 Requisitos Prerrequisito


Los requisitos describen en detalle lo que se supone que debe hacer un sistema de software y son el
primer paso hacia una solución. La actividad de requisitos también se conoce como "desarrollo de
requisitos", "análisis de requisitos", "análisis", "definición de requisitos", "requisitos de software",
"especificación", "especificaciones funcionales" y "especificaciones".

¿Por qué tener requisitos oficiales?


Un conjunto explícito de requisitos es importante por varias razones.

Los requisitos explícitos ayudan a garantizar que el usuario, y no el programador, dirija la


funcionalidad del sistema. Si los requisitos son explícitos, el usuario puede revisarlos y
aceptarlos. Si no lo son, el programador generalmente termina tomando decisiones sobre los
requisitos durante la programación. Los requisitos explícitos le impiden adivinar lo que quiere
el usuario.

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.

El mito de los requisitos estables


Los requisitos son como el agua. Los requisitos estables son el santo grial del desarrollo de software. Con requisitos
Son más fáciles de construir
estables, un proyecto puede pasar de la arquitectura al diseño, a la codificación y a las
cuando están congelados.

—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.

Manejo de cambios en los requisitos durante la construcción


Aquí hay varias cosas que puede hacer para aprovechar al máximo los requisitos cambiantes durante la
construcción:

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"

en la Sección 5.4 y la Sección 29.3,


prototipos evolutivos lo ayuda a explorar los requisitos de un sistema antes de enviar sus fuerzas
"Estrategias de integración para construirlo. La entrega evolutiva es un enfoque que entrega el sistema en etapas. Puede
incremental".
construir un poco, obtener un poco de retroalimentación de sus usuarios, ajustar un poco su diseño,
hacer algunos cambios y construir un poco más. La clave es usar ciclos de desarrollo cortos para que
puedas responder a tus usuarios rápidamente.

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

menudo causadas por diferencias en


proyecto. Los requisitos que parecían buenas ideas cuando se consideraban "características"
el tamaño del proyecto), consulte el pueden parecer ideas terribles cuando se evalúa el "valor comercial incremental". Los
Capítulo 27, "Cómo el tamaño del
programadores que recuerdan considerar el impacto comercial de sus decisiones valen su
programa afecta la construcción".
peso en oro, aunque me complacerá recibir mi comisión por este asesoramiento en efectivo.
42 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba

cc2e.com/0323 Lista de verificación: requisitos


La lista de verificación de requisitos contiene una lista de preguntas que debe hacerse sobre
los requisitos de su proyecto. Este libro no le dice cómo hacer un buen desarrollo de
requisitos, y la lista tampoco le dirá cómo hacerlo. Use la lista como una verificación de
cordura en el momento de la construcción para determinar qué tan sólido es el terreno sobre
el que se encuentra, dónde se encuentra en la escala de requisitos de Richter.

No todas las preguntas de la lista de verificación se aplicarán a su proyecto. Si está trabajando


en un proyecto informal, encontrará algunos en los que ni siquiera necesita pensar.
Encontrará otros en los que necesita pensar pero no necesita responder formalmente. Sin
embargo, si está trabajando en un proyecto grande y formal, es posible que deba considerar
cada uno.

Requisitos funcionales específicos


- ¿Se especifican todas las entradas al sistema, incluida su fuente, precisión,
rango de valores y frecuencia?

- ¿Se especifican todas las salidas del sistema, incluido su destino,


precisión, rango de valores, frecuencia y formato?

- ¿Se especifican todos los formatos de salida para páginas web, informes, etc.?

- ¿Están especificadas todas las interfaces externas de hardware y software?

- ¿Están especificadas todas las interfaces de comunicación externas, incluidos los protocolos de

comunicación, verificación de errores y protocolos de comunicación?

- ¿Están especificadas todas las tareas que el usuario quiere realizar?

- ¿Se especifican los datos utilizados en cada tarea y los datos resultantes de cada tarea?

Requisitos específicos no funcionales (de calidad)


- ¿Se especifica el tiempo de respuesta esperado, desde el punto de vista del usuario, para
todas las operaciones necesarias?

- ¿Se especifican otras consideraciones de tiempo, como el tiempo de procesamiento, la tasa de

transferencia de datos y el rendimiento del sistema?

- ¿Está especificado el nivel de seguridad?

- ¿Se especifica la confiabilidad, incluidas las consecuencias de la falla del software,


la información vital que debe protegerse de la falla y la estrategia para la
detección y recuperación de errores?

- ¿Se especifican la memoria mínima de la máquina y el espacio libre en disco?

- ¿Se especifica la capacidad de mantenimiento del sistema, incluida su capacidad para


adaptarse a los cambios en la funcionalidad específica, los cambios en el entorno
operativo y los cambios en sus interfaces con otro software?

- ¿Está incluida la definición de éxito? ¿De fracaso?


3.5 Prerrequisito de arquitectura 43

Requisitos Calidad
- ¿Los requisitos están escritos en el idioma del usuario? ¿Los usuarios lo creen
así?

- ¿Cada requisito evita conflictos con otros requisitos?


- ¿Se especifican compensaciones aceptables entre atributos en
competencia, por ejemplo, entre robustez y corrección?

- ¿Los requisitos evitan especificar el diseño?


- ¿Están los requisitos en un nivel de detalle bastante consistente? ¿Debería
especificarse algún requisito con más detalle? ¿Debe especificarse algún requisito
con menos detalle?

- ¿Son los requisitos lo suficientemente claros como para entregarlos a un grupo


independiente para la construcción y aún así ser entendidos? ¿Los desarrolladores 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?

- ¿Cada requisito es comprobable? ¿Será posible realizar pruebas


independientes para determinar si se ha satisfecho cada requisito?

- ¿Se especifican todos los cambios posibles a los requisitos, incluida la


probabilidad de cada cambio?

Completitud de los requisitos


- Cuando la información no está disponible antes de que comience el desarrollo, ¿se
especifican las áreas incompletas?

- ¿Están completos los requisitos en el sentido de que si el producto


satisface todos los requisitos, será aceptable?

- ¿Se siente cómodo con todos los requisitos? ¿Ha eliminado


requisitos que son imposibles de implementar y los ha incluido solo
para apaciguar a su cliente oa su jefe?

3.5 Prerrequisito de arquitectura


Referencia cruzadaPara obtener más La arquitectura de software es la parte de alto nivel del diseño de software, el marco que contiene las
información sobre el diseño en todos los
partes más detalladas del diseño (Buschman et al. 1996; Fowler 2002; Bass Clements, Kazman 2003;
niveles, consulte los Capítulos 5

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

Componentes arquitectónicos típicos


Referencia cruzadaPara obtener detalles Muchos componentes son comunes a las buenas arquitecturas de sistemas. Si está
sobre el programa de nivel inferior
construyendo todo el sistema usted mismo, su trabajo en la arquitectura se superpondrá con
diseño, consulte los
Capítulos 5 a 9. su trabajo en el diseño más detallado. En tal caso, al menos debería pensar en cada
componente arquitectónico. Si está trabajando en un sistema diseñado por otra persona,
debería poder encontrar los componentes importantes sin un sabueso, una gorra de
acechador de ciervos y una lupa. En cualquier caso, aquí están los componentes
arquitectónicos a considerar.

Organización del programa

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.

En la arquitectura, debe encontrar evidencia de que se consideraron alternativas a la organización


final y encontrar las razones para elegir la organización final sobre sus alternativas. Es frustrante
trabajar en una clase cuando parece que el rol de la clase en el sistema no ha sido claramente
concebido. Al describir las alternativas organizativas, la arquitectura proporciona la justificación de la
organización del sistema y muestra que cada clase se ha considerado cuidadosamente. Una revisión
de las prácticas de diseño encontró que la justificación del diseño es al menos tan importante para el
mantenimiento como el diseño mismo (Rombach 1990).

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

"Niveles de diseño" en la Sección 5.2.


subsistema que consta de muchas clases. Cada bloque de construcción es una clase, o es una
colección de clases o rutinas que funcionan juntas en funciones de alto nivel, como interactuar con el
usuario, mostrar páginas web, interpretar comandos, encapsular reglas comerciales o acceder a
datos. Cada característica enumerada en los requisitos debe estar cubierta por al menos un bloque
de construcción. Si dos o más bloques de construcción reclaman una función, sus reclamos deben
cooperar, no entrar en conflicto.

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

clave de la ocultación de información. Para


de responsabilidad de otros bloques de construcción. Al minimizar lo que sabe cada bloque de
obtener más información, consulte construcción sobre los otros bloques de construcción, localiza la información sobre el diseño en
"Ocultar secretos (ocultar información)" en
bloques de construcción únicos.
la Sección 5.3.
46 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba

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.

La arquitectura debe especificar la organización y el contenido de alto nivel de cualquier base de


datos utilizada. La arquitectura debe explicar por qué una sola base de datos es preferible a
múltiples bases de datos (o viceversa), explicar por qué una base de datos es preferible a archivos
planos, identificar posibles interacciones con otros programas que acceden a los mismos datos,
explicar qué vistas se han creado en los datos , y así.

Reglas del negocio

Si la arquitectura depende de reglas comerciales específicas, debe identificarlas y describir el


impacto que tienen las reglas en el diseño del sistema. Por ejemplo, supongamos que se requiere
que el sistema siga una regla de negocios de que la información del cliente no debe ser
3.5 Prerrequisito de arquitectura 47

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.

Diseño de interfaz de usuario

La interfaz de usuario a menudo se especifica en el momento de los requisitos. Si no es así, debe


especificarse en la arquitectura del software. La arquitectura debe especificar los elementos principales de
los formatos de página web, las GUI, las interfaces de línea de comandos, etc. La arquitectura cuidadosa de
la interfaz de usuario marca la diferencia entre un programa que gusta mucho y uno que nunca se usa.

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

seguro, 2ª ed. (Howard y LeBlanc


codificación deben desarrollarse teniendo en cuenta las implicaciones de seguridad, incluidos enfoques para
2003), así como la edición de el manejo de búferes, reglas para el manejo de datos no confiables (entrada de datos de usuarios, cookies,
enero de 2002 de Software IEEE.
datos de configuración y otras interfaces externas), cifrado, nivel de detalle contenido en mensajes de error,
protección datos secretos que están en la memoria y otras cuestiones.
48 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba

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,

consulte Connie Smith'sIngeniería de


las prioridades entre los recursos, incluida la velocidad frente a la memoria frente al costo.
Rendimiento de

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

Si se espera que el sistema comparta datos o recursos con otro software o


hardware, la arquitectura debe describir cómo se logrará.

Internacionalización/Localización

La “internacionalización” es la actividad técnica de preparar un programa para soportar


múltiples locales. La internacionalización a menudo se conoce como "I18n" porque el primer y
último carácter de "internacionalización" son "I" y "N" y porque hay 18 letras en el medio de la
palabra. La “localización” (conocida como “L10n” por la misma razón) es la actividad de traducir
un programa para admitir un idioma local específico.

Los problemas de internacionalización merecen atención en la arquitectura de un sistema interactivo.


La mayoría de los sistemas interactivos contienen docenas o cientos de avisos, pantallas de estado,
mensajes de ayuda, mensajes de error, etc. Se deben estimar los recursos utilizados por las cadenas.
Si el programa se utilizará comercialmente, la arquitectura debe mostrar que se han considerado los
problemas típicos de cadenas y conjuntos de caracteres, incluido el conjunto de caracteres utilizado
(ASCII, DBCS, EBCDIC, MBCS, Unicode, ISO 8859, etc.), tipos de cadenas utilizadas (cadenas C, cadenas
de Visual Basic, etc.), manteniendo las cadenas sin cambiar el código y traduciendo las cadenas a
idiomas extranjeros con un impacto mínimo en el código y la interfaz de usuario. La arquitectura
puede decidir usar cadenas en línea en el código donde se necesitan, mantenga las cadenas en una
clase y haga referencia a ellas a través de la interfaz de clase, o almacene las cadenas en un archivo
de recursos. La arquitectura debe explicar qué opción se eligió y por qué.
3.5 Prerrequisito de arquitectura 49

De entrada y salida

La entrada/salida (E/S) es otra área que merece atención en la arquitectura. La arquitectura


debe especificar un esquema de lectura anticipado, retrospectivo o justo a tiempo. Y debe
describir el nivel en el que se detectan los errores de E/S: a nivel de campo, registro, flujo o
archivo.

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:

- ¿El procesamiento de errores es correctivo o simplemente detectivo? Si es correctivo, el


programa puede intentar recuperarse de los errores. Si es meramente detectivo, el programa
puede continuar procesando como si nada, o puede cerrarse. En cualquier caso, debe notificar
al usuario que detectó un error.

- ¿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

implicaciones en la interfaz de usuario.

- ¿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).

- ¿Cuáles son las convenciones para el manejo de mensajes de error? Si la arquitectura no


especifica una estrategia única y consistente, la interfaz de usuario parecerá un confuso
collage de macarrones con frijoles secos de diferentes interfaces en diferentes partes del
programa. Para evitar tal apariencia, la arquitectura debería establecer convenciones para los
mensajes de error.

- ¿Cómo se manejarán las excepciones? La arquitectura debe abordar cuándo el


código puede generar excepciones, dónde se detectarán, cómo se registrarán,
cómo se documentarán, etc.
50 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba

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?

- ¿Desea utilizar el mecanismo de manejo de excepciones integrado de su entorno o crear


uno propio? El hecho de que un entorno tenga un enfoque particular de manejo de
errores no significa que sea el mejor enfoque para sus requisitos.

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

julio de 2001 deSoftware IEEE.


recuperarse de ellos si es posible y contener sus efectos negativos si no.
Además de proporcionar una

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.

Especificar un enfoque para la sobreingeniería es particularmente importante porque muchos


programadores sobrediseñan sus clases automáticamente, por un sentido de orgullo
profesional. Al establecer expectativas explícitamente en la arquitectura, puede evitar el
fenómeno en el que algunas clases son excepcionalmente robustas y otras apenas
adecuadas.

Decisiones de compra frente a construcción

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,

consulte "Bibliotecas de códigos" en la


bases de datos, procesadores de imágenes, gráficos y componentes de diagramas, componentes de comunicaciones
Sección 30.3. por Internet, componentes de seguridad y cifrado, herramientas de hoja de cálculo, herramientas de procesamiento

de texto: la lista es casi interminable. Una de las mayores ventajas de la programación en entornos GUI modernos es

la cantidad de funcionalidad que obtiene automáticamente: clases de gráficos, administradores de cuadros de

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

medida que se agregan nuevas


también las más fáciles de implementar. Si es probable que haya cambios en los formatos de entrada
características o usos a un sistema. o salida, el estilo de interacción del usuario o los requisitos de procesamiento, la arquitectura debe
—Fernando J. Corbató mostrar que todos los cambios se han anticipado y que los efectos de cualquier cambio individual se
limitarán a un pequeño número de clases. El plan de cambios de la arquitectura puede ser tan simple
como poner números de versión en archivos de datos, reservar campos para uso futuro o diseñar
archivos para que pueda agregar nuevas tablas. Si se utiliza un generador de código, la arquitectura
debe mostrar que los cambios anticipados están dentro de las capacidades del generador de código.

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

vinculante de manera consciente" en la


codificada.sipruebas Podría especificar que los datos de la tabla se mantengan en un archivo externo
Sección 5.3. en lugar de codificarse dentro del programa, lo que permite cambios en el programa sin volver a
compilar.

Calidad arquitectónica general

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

Sección 20.1, “Características de la


excluir todas las posibles alternativas de diseño.
calidad del software”.
La arquitectura debe ser un conjunto conceptual pulido con pocas adiciones ad hoc. La tesis
central del libro de ingeniería de software más popular de todos los tiempos,El Hombre-Mes
Mítico, es que el problema esencial con los grandes sistemas es mantener su integridad
conceptual (Brooks 1995). Una buena arquitectura debe adaptarse al problema. Cuando
observe la arquitectura, debería estar complacido por lo natural y fácil que parece la solución.
No debería parecer que el problema y la arquitectura se han unido a la fuerza con cinta
adhesiva.
3.5 Prerrequisito de arquitectura 53

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”.

Una buena arquitectura de software es en gran medida independiente de la máquina y el


lenguaje. Es cierto que no se puede ignorar el entorno de construcción. Sin embargo, al ser lo
más independiente posible del entorno, evita la tentación de sobrediseñar el sistema o de
hacer un trabajo que puede hacer mejor durante la construcción. Si el propósito de un
programa es ejercitar una máquina o lenguaje específico, esta directriz no se aplica.

La arquitectura debe trazar la línea entre subespecificar y sobreespecificar el sistema.


Ninguna parte de la arquitectura debe recibir más atención de la que merece, o estar
sobrediseñada. Los diseñadores no deben prestar atención a una parte a expensas de
otra. La arquitectura debe abordar todos los requisitos sin chapado en oro (sin contener
elementos que no sean necesarios).

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).

Finalmente, no debería sentirse incómodo con ninguna parte de la arquitectura. No debe


contener nada solo para complacer al jefe. No debe contener nada que sea difícil de
entender. Usted es quien lo implementará; si no tiene sentido para usted, ¿cómo puede
implementarlo?
Traducido del inglés al español - www.onlinedoctranslator.com

54 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba

cc2e.com/0337 Lista de verificación: Arquitectura

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.

Temas Arquitectónicos Específicos


- ¿La organización general del programa es clara, incluida una buena descripción y
justificación de la arquitectura?

- ¿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?

- ¿Están descritas y justificadas las clases más críticas?

- ¿Se describe y justifica el diseño de datos?

- ¿Se especifica la organización y el contenido de la base de datos?

- ¿Se identifican todas las reglas comerciales clave y se describe su impacto en el


sistema?

- ¿Se describe una estrategia para el diseño de la interfaz de usuario?

- ¿La interfaz de usuario está modularizada para que los cambios no afecten al resto del
programa?

- ¿Se describe y justifica una estrategia para el manejo de E/S?

- ¿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.?

- ¿Se describen los requisitos de seguridad de la arquitectura?

- ¿La arquitectura establece presupuestos de espacio y velocidad para cada clase,


subsistema o área de funcionalidad?

- ¿La arquitectura describe cómo se logrará la escalabilidad?

- ¿La arquitectura aborda la interoperabilidad?

- ¿Se describe una estrategia de internacionalización/localización?

- ¿Se proporciona una estrategia coherente de manejo de errores?

- ¿Está definido el enfoque de la tolerancia a fallas (si es necesario)?


3.6 Cantidad de tiempo para dedicar a los requisitos previos de Upstream 55

- ¿Se ha establecido la viabilidad técnica de todas las partes del sistema?

- ¿Se especifica un enfoque para la sobreingeniería?

- ¿Se incluyen las decisiones necesarias de comprar o construir?

- ¿Describe la arquitectura cómo se hará el código reutilizado para ajustarse a otros


objetivos arquitectónicos?

- ¿La arquitectura está diseñada para adaptarse a los posibles cambios?

Calidad arquitectónica general


- ¿La arquitectura tiene en cuenta todos los requisitos?

- ¿Alguna parte tiene una arquitectura excesiva o insuficiente? ¿Se establecen explícitamente las
expectativas en esta área?

- ¿Toda la arquitectura se une conceptualmente?


- ¿El diseño de alto nivel es independiente de la máquina y el lenguaje que se
utilizará para implementarlo?

- ¿Se proporcionan las motivaciones para todas las decisiones importantes?

- ¿Está usted, como programador que implementará el sistema, cómodo con


la arquitectura?

3.6 Cantidad de tiempo para dedicar a los requisitos previos de Upstream


Referencia cruzadalos La cantidad de tiempo que se dedica a la definición del problema, los requisitos y la arquitectura del software
la cantidad de tiempo que dedique a
varía según las necesidades de su proyecto. En general, un proyecto bien administrado dedica entre el 10 y
los requisitos previos dependerá de

su tipo de proyecto. Para obtener


el 20 por ciento de su esfuerzo y entre el 20 y el 30 por ciento de su cronograma a los requisitos, la
detalles sobre cómo adaptar los arquitectura y la planificación inicial (McConnell 1998, Kruchten 2000). Estas cifras no incluyen el tiempo para
requisitos previos a su
el diseño detallado, eso es parte de la construcción.
proyecto, consulte la Sección 3.2,

“Determine el tipo de software en el


Si los requisitos son inestables y está trabajando en un gran proyecto formal, probablemente
que está trabajando”, anteriormente

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.

Robertson, Suzanne y James Robertson.Dominar el proceso de requisitos. Reading, MA:


Addison-Wesley, 1999. Esta es una buena alternativa al libro de Wiegers para los
profesionales de requisitos más avanzados.

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

Norma IEEE 830-1998. Práctica recomendada de IEEE para especificaciones de requisitos de


software. Los Alamitos, CA: IEEE Computer Society Press. Este documento es la guía IEEE-ANSI
para escribir especificaciones de requisitos de software. Describe lo que debe incluirse en el
documento de especificaciones y muestra varios esquemas alternativos para uno.

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

Otras buenas alternativas incluyen las siguientes:

Lauesen, Soren.Requisitos de software: estilos y técnicas. Boston, MA: Addison-


Wesley, 2002.

Kovitz, Benjamín L.Requisitos prácticos de software: un manual de contenido y estilo.


Compañía de Publicaciones Manning, 1998.

Cockburn, Alistair.Escritura de casos de uso efectivos. Boston, MA: Addison-Wesley, 2000.

cc2e.com/0372 Arquitectura de software

En los últimos años se han publicado numerosos libros sobre arquitectura de software. Aquí están
algunos de los mejores:

Bass, Len, Paul Clements y Rick Kazman.Arquitectura de software en la práctica, 2ª ed.


Boston, MA: Addison-Wesley, 2003.

Buschman, Frank, et al.Arquitectura de software orientada a patrones, volumen 1: un sistema de


patrones. Nueva York, NY: John Wiley & Sons, 1996.

Clemente, Paul, ed.Documentación de arquitecturas de software: vistas y más allá. Boston, MA:
Addison-Wesley, 2003.

Clements, Paul, Rick Kazman y Mark Klein.Evaluación de arquitecturas de software: métodos y


estudios de casos. Boston, MA: Addison-Wesley, 2002.

Fowler, Martín.Patrones de arquitectura de aplicaciones empresariales. Boston, MA: Addison-


Wesley, 2002.

Jacobson, Ivar, Grady Booch y James Rumbaugh.El proceso de desarrollo de software


unificado. Reading, MA: Addison-Wesley, 1999.

Norma IEEE 1471-2000. Práctica recomendada para la descripción arquitectónica de sistemas


intensivos en software. Los Alamitos, CA: IEEE Computer Society Press. Este documento es la
guía IEEE-ANSI para crear especificaciones de arquitectura de software.
58 Capítulo 3: Medir dos veces, cortar una vez: requisitos previos aguas arriba

cc2e.com/0379 Enfoques generales de desarrollo de software

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.

McConnell, Steve.Guía de supervivencia de proyectos de software. Redmond, WA: Microsoft Press,


1998. Este libro presenta una forma particular de realizar un proyecto. El enfoque presentado
enfatiza la planificación inicial deliberada, el desarrollo de requisitos y el trabajo de arquitectura
seguido de una ejecución cuidadosa del proyecto. Proporciona previsibilidad a largo plazo de costos
y cronogramas, alta calidad y una cantidad moderada de flexibilidad.

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.

Beck, Kent.Explicación de la programación extrema: aceptar el cambio. Reading, MA: Addison-


Wesley, 2000. Beck describe un enfoque altamente iterativo que se enfoca en desarrollar
requisitos y diseños de manera iterativa, junto con la construcción. El enfoque de
programación extrema ofrece poca previsibilidad a largo plazo pero proporciona un alto
grado de flexibilidad.

Gil, Tom.Principios de Gestión de Ingeniería de Software. Wokingham, Inglaterra: Addison-Wesley,


1988. El enfoque de Gilb explora problemas críticos de planificación, requisitos y arquitectura al
principio de un proyecto y luego adapta continuamente los planes del proyecto a medida que avanza
el proyecto. Este enfoque proporciona una combinación de previsibilidad a largo plazo, alta calidad y
un alto grado de flexibilidad. Requiere más sofisticación que los enfoques descritos enGuía de
supervivencia de proyectos de softwareyExplicación de la programación extrema: aceptar el cambio.

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.

cc2e.com/0386 Lista de verificación: requisitos previos ascendentes

- ¿Ha identificado el tipo de proyecto de software en el que está trabajando y ha


adaptado su enfoque de manera adecuada?

- ¿Están los requisitos lo suficientemente bien definidos y estables para comenzar la construcción?

(Consulte la lista de verificación de requisitos para obtener más detalles).

- ¿La arquitectura está lo suficientemente bien definida para comenzar la construcción? (Consulte la lista de verificación

de la arquitectura para obtener más detalles).

- ¿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

sus actividades de preparación estén reduciendo los riesgos, no aumentándolos.

- 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 trabajo de requisitos, es posible que se haya perdido detalles


importantes del problema. Los cambios de requisitos cuestan de 20 a 100 veces más en las
etapas posteriores a la construcción que antes, así que asegúrese de que los requisitos sean
correctos antes de comenzar a programar.

- 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.

- Comprenda qué enfoque se ha tomado para los requisitos previos de construcción en


su proyecto y elija su enfoque de construcción en consecuencia.
Capítulo 4

Decisiones clave de construcción


cc2e.com/0489 Contenido

- 4.1 Elección del lenguaje de programación: página 61

- 4.2 Convenciones de programación: página 66

- 4.3 Su ubicación en la ola tecnológica: página 66

- 4.4 Selección de las principales prácticas de construcción: página 69

Temas relacionados

- Prerrequisitos ascendentes: Capítulo 3

- Determine el tipo de software en el que está trabajando: Sección 3.2

- Cómo el tamaño del programa afecta la construcción: Capítulo 27

- Gestión de la construcción: Capítulo 28

- Diseño de software: Capítulo 5 y Capítulos 6 a 9

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".

4.1 Elección del lenguaje de programación


Al liberar al cerebro de todo trabajo innecesario, una buena notación lo libera para
concentrarse en problemas más avanzados y, en efecto, aumenta el poder mental de la
raza. Antes de la introducción de la notación árabe, la multiplicación era difícil, e incluso
la división de números enteros requería las más altas facultades matemáticas.
Probablemente nada en el mundo moderno habría asombrado más a un matemático
griego que saber que... una gran proporción de la població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.

—Alfred North Whitehead

El lenguaje de programación en el que se implementará el sistema debe ser de


sumo interés ya que estará inmerso en él desde el inicio de la construcción hasta el
final.

Los estudios han demostrado que la elección del lenguaje de programación afecta la productividad y la calidad del

código de varias maneras.

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.

Tabla 4-1 Proporción de declaraciones en lenguaje de alto nivel a código C equivalente

Idioma Nivel relativo a C


C 1
C++ 2.5
Fortran 95 2
Java 2.5
Perl 6
Pitón 6
Charla 6
microsoft visual basic 4.5
Fuente: Adaptado deEstimación de costos de software(Jones 1998),Estimación de costos de software con Cocomo II
(Boehm 2000) y “Una comparación empírica de siete lenguajes de programación” (Prechelt 2000).
4.1 Elección del lenguaje de programación 63

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.

La evidencia del efecto de los lenguajes de programación en el pensamiento de los programadores es


común. Una historia típica es la siguiente: “Estábamos escribiendo un nuevo sistema en C++, pero la
mayoría de nuestros programadores no tenían mucha experiencia en C++. Procedían de fondos de
Fortran. Escribieron código que compilaba en C++, pero en realidad estaban escribiendo Fortran
disfrazado. Extendieron C++ para emular las malas funciones de Fortran (como gotos y datos
globales) e ignoraron el rico conjunto de capacidades orientadas a objetos de C++”. Este fenómeno se
ha informado en toda la industria durante muchos años (Hanson 1984, Yourdon 1986a).

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

JavaScript es un lenguaje de secuencias de comandos interpretado que originalmente estaba vagamente


relacionado con Java. Se utiliza principalmente para la programación del lado del cliente, como agregar funciones
simples y aplicaciones en línea a las páginas web.

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).

4.2 Convenciones de programación


Referencia cruzadaPara obtener más En software de alta calidad, puede ver una relación entre la integridad conceptual de la
detalles sobre el poder de las
arquitectura y su implementación de bajo nivel. La implementación debe ser consistente con
convenciones, consulte las Secciones

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.

En un programa complejo, las pautas arquitectónicas dan al programa equilibrio estructural y


las pautas de construcción brindan armonía de bajo nivel, articulando cada clase como una
parte fiel de un diseño integral. Cualquier programa grande requiere una estructura de control
que unifique los detalles de su lenguaje de programación. Parte de la belleza de una gran
estructura es la forma en que sus partes detalladas confirman las implicaciones de su
arquitectura. Sin una disciplina unificadora, su creación será un revoltijo de variaciones de
estilo descuidadas. Tales variaciones ponen a prueba su cerebro, y solo para comprender las
diferencias de estilo de codificación que son esencialmente arbitrarias. Una clave para una
programación exitosa es evitar variaciones arbitrarias para que su cerebro pueda concentrarse
en las variaciones que realmente se necesitan. Para más información sobre esto,

¿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.

4.3 Su ubicación en la ola tecnológica


Durante mi carrera, he visto ascender la estrella de la PC mientras que la estrella de la computadora central
se hundió hacia el horizonte. He visto programas GUI reemplazar programas basados en caracteres. Y he
visto la Web ascender mientras que Windows decae. Solo puedo suponer que para cuando leas
4.3 Su ubicación en la ola tecnológica 67

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.

Ejemplo de programacióndentroun idioma


En los primeros días de Visual Basic, estaba frustrado porque quería mantener la lógica
empresarial, la interfaz de usuario y la base de datos separadas en el producto que estaba
desarrollando, pero no había ninguna forma integrada de hacerlo en el lenguaje. . Sabía que si
no tenía cuidado, con el tiempo, algunos de mis "formularios" de Visual Basic terminarían
conteniendo lógica comercial, algunos formularios contendrían código de base de datos y
otros no contendrían ninguno; el código se encuentra en qué lugar. Acababa de completar un
proyecto de C++ que había hecho un mal trabajo al separar esos problemas y no quería
experimentar el déjà vu de esos dolores de cabeza en un idioma diferente.

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.

Comprender la distinción entre programar en un lenguaje y programar en uno es fundamental


para comprender este libro. La mayoría de los principios de programación importantes no
dependen de lenguajes específicos sino de la forma en que los usa. Si su idioma carece de
PUNTO CLAVE
construcciones que desee utilizar o es propenso a otros tipos de problemas, intente
compensarlos. Inventa tus propias convenciones de codificación, estándares, bibliotecas de
clases y otros aumentos.

4.4 Selección de las principales prácticas de construcción


Parte de la preparación para la construcción es decidir cuál de las muchas buenas prácticas disponibles
enfatizará. Algunos proyectos usan programación en pares y desarrollo de prueba primero, mientras que
otros usan desarrollo en solitario e inspecciones formales. Cualquier combinación de técnicas puede
funcionar bien, dependiendo de las circunstancias específicas del proyecto.

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.

cc2e.com/0496 Lista de verificación: principales prácticas de construcción

Codificación

- ¿Ha definido cuánto diseño se hará por adelantado y cuánto se hará


en el teclado, mientras se escribe el código?
- ¿Ha definido convenciones de codificación para nombres, comentarios y diseño?

- ¿Ha definido prácticas de codificación específicas implícitas en la arquitectura, como


cómo se manejarán las condiciones de error, cómo se abordará la seguridad, qué
convenciones se usarán para las interfaces de clase, qué estándares se aplicarán al
código reutilizado, cuánto considerar el rendimiento? mientras codifica, y así
sucesivamente?

- ¿Ha identificado su ubicación en la ola tecnológica y ha ajustado su enfoque


para que coincida? Si es necesario, ¿ha identificado cómo programarádentro
el lenguaje en lugar de estar limitado por la programaciónen¿eso?

Trabajo en equipo

- ¿Ha definido un procedimiento de integración, es decir, ha definido los pasos


específicos que debe seguir un programador antes de verificar el código en las
fuentes maestras?

- ¿Los programadores programarán en parejas, individualmente o alguna combinación


de los dos?
70 Capítulo 4: Decisiones clave de construcción

Referencia cruzadaPara obtener Seguro de calidad


más detalles sobre la garantía de

calidad, consulte el Capítulo 20, “El


- ¿Escribirán los programadores casos de prueba para su código antes de escribir el código
panorama de la calidad del software”. en sí?

- ¿Escribirán los programadores pruebas unitarias para su código independientemente de


si las escriben primero o último?

- ¿Los programadores revisarán su código en el depurador antes de


registrarlo?

- ¿Los programadores probarán la integración de su código antes de registrarlo?

- ¿Los programadores revisarán o inspeccionarán el código de los demás?

Referencia cruzadaPara obtener más detalles


Instrumentos
sobre las herramientas, consulte el Capítulo 30,

“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?

- ¿Seleccionó un marco como J2EE o Microsoft .NET o decidió


explícitamente no utilizar un marco?
- ¿Ha decidido si permitir el uso de funciones de lenguaje no estándar?

- ¿Ha identificado y adquirido otras herramientas que utilizará: editor, herramienta de


refactorización, depurador, marco de prueba, verificador de sintaxis, etc.?

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.

- Establezca convenciones de programación antes de comenzar a programar. Es casi imposible


cambiar el código para que coincida con ellos más tarde.

- 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.

- Su posición en la ola tecnológica determina qué enfoques serán efectivos, o incluso


posibles. Identifique dónde se encuentra en la ola tecnológica y ajuste sus planes y
expectativas en consecuencia.
Parte II
Creación de código de alta calidad

En esta parte:

Capítulo 5: Diseño en la Construcción. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .73


Capítulo 6: Clases obreras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .125 Capítulo 7:
Rutinas de alta calidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .161 Capítulo 8:
Programación defensiva. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .187 Capítulo 9: El
proceso de programación del pseudocódigo. . . . . . . . . . . . . . . . . .215
Capítulo 5

Diseño en Construcción
cc2e.com/0578 Contenido

- 5.1 Desafíos de diseño: página 74

- 5.2 Conceptos clave de diseño: página 77

- 5.3 Elementos básicos del diseño: heurística: página 87

- 5.4 Prácticas de diseño: página 110

- 5.5 Comentarios sobre Metodologías Populares: página 118

Temas relacionados

- Arquitectura de software: Sección 3.5

- Clases obreras: Capítulo 6

- Características de las rutinas de alta calidad: Capítulo 7

- Programación defensiva: Capítulo 8

- Refactorización: Capítulo 24

- Cómo el tamaño del programa afecta la construcción: Capítulo 27

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

pequeños, consulte el Capítulo 27, “Cómo


los detalles. Podría ser dibujar diagramas de algunas relaciones de clase antes de codificarlas. Podría ser
el tamaño del programa afecta la preguntarle a otro programador qué patrón de diseño parece una mejor opción. Independientemente de
construcción”.
cómo se haga, los proyectos pequeños se benefician de un diseño cuidadoso al igual que los proyectos más
grandes, y reconocer el diseño como una actividad explícita maximiza el beneficio que recibirá de él.

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.

5.1 Desafíos de diseño


Referencia cruzadaLa La frase "diseño de software" significa la concepción, invención o invención de un esquema para convertir
diferencia entre procesos
una especificación para software de computadora en software operativo. El diseño es la actividad que
heurísticos y deterministas se
describe en el Capítulo 2, vincula los requisitos con la codificación y la depuración. Un buen diseño de nivel superior proporciona una
“Metáforas para una mejor estructura que puede contener de manera segura múltiples diseños de nivel inferior. Un buen diseño es útil
comprensión del desarrollo de
en proyectos pequeños e indispensable en proyectos grandes.
software”.

El diseño también está marcado por numerosos desafíos, que se describen en esta sección.

El diseño es un problema perverso


La imagen del diseñador de software Horst Rittel y Melvin Webber definieron un problema “retorcido” como aquel que podía definirse
que deriva su diseño de una forma
claramente solo resolviéndolo o resolviendo parte de él (1973). Esta paradoja implica, esencialmente,
racional y sin errores a partir de una

declaración de requisitos es bastante


que tienes que “resolver” el problema una vez para definirlo claramente y luego resolverlo de nuevo
poco realista. Ningún sistema se ha para crear una solución que funcione. Este proceso ha sido la maternidad y el pastel de manzana en
desarrollado nunca de esa manera, y
el desarrollo de software durante décadas (Peters y Tripp 1976).
probablemente ninguno lo hará

nunca. Incluso el pequeño programa


En mi parte del mundo, un ejemplo dramático de un problema tan perverso fue el diseño del puente
de desarrollo

Los detalles que se muestran en


original de Tacoma Narrows. En el momento en que se construyó el puente, la consideración principal
los libros de texto y los periódicos al diseñar un puente era que fuera lo suficientemente resistente para soportar la carga prevista. En el
son irreales. Han sido revisados
caso del puente de Tacoma Narrows, el viento creó una ondulación armónica inesperada de lado a
y pulidos hasta que el autor nos

ha mostrado lo que desearía


lado. Un día ventoso de 1940, la onda creció sin control hasta que el puente colapsó, como se
haber hecho, no lo que realmente muestra en la Figura 5-1.
sucedió.

—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 es un proceso descuidado (incluso si produce un resultado ordenado)

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

proceso de diseño racional: cómo


barato cometer errores y corregir diseños que cometer los mismos errores, reconocerlos
y por qué falsificarlo" (Parnas y después de codificar y tener que corregir el código completo. El diseño es descuidado porque
Clements 1986).
una buena solución a menudo es solo sutilmente diferente de una mala.
76 Capítulo 5: Diseño en la Construcción

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?"

¿Suficiente?" en la Sección 5.4 más


diseño formal y cuánto se debe dejar por hacer en el teclado? ¿Cuándo terminaste? Dado que
adelante en este capítulo. el diseño es abierto, la respuesta más común a esa pregunta es "Cuando te quedas sin
tiempo".

El diseño se trata de compensaciones y prioridades

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 implica restricciones


El objetivo del diseño es en parte crear posibilidades y en parterestringir posibilidades. Si la gente
tuviera tiempo, recursos y espacio infinitos para construir estructuras físicas, vería edificios increíbles
con una habitación para cada zapato y cientos de habitaciones. Así es como el software puede
resultar sin restricciones impuestas deliberadamente. Las limitaciones de los recursos limitados para
la construcción de edificios fuerzan simplificaciones de la solución que, en última instancia, mejoran
la solución. El objetivo en el diseño de software es el mismo.

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.

El diseño es un proceso heurístico

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).

5.2 Conceptos clave de diseño


Un buen diseño depende de la comprensión de un puñado de conceptos clave. Esta sección analiza
el papel de la complejidad, las características deseables de los diseños y los niveles de diseño.

El imperativo técnico principal del software: gestionar la complejidad


Referencia cruzadaPara una discusión Para comprender la importancia de administrar la complejidad, es útil consultar el documento
sobre la forma en que la complejidad
histórico de Fred Brooks, "No Silver Bullets: Essence and Accidents of Software
afecta los problemas de programación

distintos del diseño, consulte


Engineering" (1987).
Sección 34.1, “Conquista
Complejidad."
Dificultades accidentales y esenciales

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

en el desarrollo de onda tardía. Para


gran medida en la evolución del lenguaje ensamblador a los lenguajes de tercera generación y su
obtener más información, consulte la importancia ha disminuido progresivamente desde entonces. Las dificultades accidentales relacionadas con
Sección 4.3, “Su ubicación en la ola
las computadoras no interactivas se resolvieron cuando los sistemas operativos de tiempo compartido
tecnológica”.
reemplazaron los sistemas de modo por lotes. Los entornos de programación integrados eliminaron aún
más las ineficiencias en el trabajo de programación que surgían de las herramientas que funcionaban mal
juntas.
78 Capítulo 5: Diseño en la Construcción

Brooks argumenta que el progreso en el software restantebásicodificultades está destinado a


ser más lento. La razón es que, en esencia, el desarrollo de software consiste en resolver todos
los detalles de un conjunto de conceptos altamente intrincados y entrelazados. Las dificultades
esenciales surgen de la necesidad de interactuar con el mundo real complejo y desordenado;
identificar de forma precisa y completa las dependencias y los casos de excepción; diseñar
soluciones que no pueden ser sólo aproximadamente correctas sino que deben ser
exactamente correctas; y así. Incluso si pudiéramos inventar un lenguaje de programación que
usara la misma terminología que el problema del mundo real que estamos tratando de
resolver, la programación seguiría siendo difícil debido al desafío de determinar con precisión
cómo funciona el mundo real. A medida que el software aborda problemas del mundo real
cada vez más grandes, las interacciones entre las entidades del mundo real se vuelven cada
vez más complejas.

La raíz de todas estas dificultades esenciales es la complejidad, tanto accidental como esencial.

Importancia de gestionar la complejidad

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.

La gestión de la complejidad es el tema técnico más importante en el desarrollo de software. En mi


opinión, es muy importante que el imperativo técnico primario del software tenga que ser gestionar
la complejidad.
PUNTO CLAVE

La complejidad no es una característica nueva del desarrollo de software. El pionero de la informática,


Edsger Dijkstra, señaló que la informática es la única profesión en la que una sola mente está obligada a
abarcar la distancia de un bit a unos pocos cientos de megabytes, una proporción de 1 a 10.9, o nueve
órdenes de magnitud (Dijkstra 1989). Esta proporción gigantesca es asombrosa. Dijkstra lo expresó de esta
manera: “En comparación con ese número de niveles semánticos, la teoría matemática promedio es casi
plana. Al evocar la necesidad de jerarquías conceptuales profundas, la computadora automática nos
confronta con un desafío intelectual radicalmente nuevo que no tiene precedentes en nuestra historia”. Por
supuesto, el software se ha vuelto aún más complejo desde 1989, y la proporción de Dijkstra de 1 a 109
fácilmente podría ser más como 1 a 1015Este Dia.
5.2 Conceptos clave de diseño 79

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

obstinadamente un método que es


software, no deberíamos tratar de meter programas completos en nuestros cráneos a la vez; debemos tratar
claramente irrelevante, al menos para de organizar nuestros programas de tal manera que podamos concentrarnos con seguridad en una parte a
cualquier observador externo. es
la vez. El objetivo es minimizar la cantidad de un programa en el que tiene que pensar en un momento dado.
como el
Puede pensar en esto como un malabarismo mental: cuantas más bolas mentales requiera el programa que
persona mecánicamente inepta

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.

—PJ Plauger A nivel de arquitectura de software, la complejidad de un problema se reduce dividiendo el


sistema en subsistemas. A los humanos les resulta más fácil comprender varias piezas simples
de información que una pieza complicada. El objetivo de todas las técnicas de diseño de
software es dividir un problema complicado en piezas simples. Cuanto más independientes
sean los subsistemas, más seguro será centrarse en un bit de complejidad a la vez. Los objetos
cuidadosamente definidos separan las preocupaciones para que pueda concentrarse en una
cosa a la vez. Los paquetes brindan el mismo beneficio a un mayor nivel de agregación.

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.

Cómo atacar la complejidad

Los diseños excesivamente costosos e ineficaces surgen de tres fuentes:

- Una solución compleja para un problema simple

- Una solución simple e incorrecta a un problema complejo

- Una solución inapropiada y compleja para un problema complejo

Como señaló Dijkstra, el software moderno es intrínsecamente complejo, y no importa cuánto


lo intente, eventualmente encontrará cierto nivel de complejidad que es inherente al problema
del mundo real. Esto sugiere un enfoque doble para gestionar la complejidad:

- 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

Características deseables de un diseño


Cuando estoy trabajando en un Un diseño de alta calidad tiene varias características generales. Si pudiera lograr todos estos
problema nunca pienso en la
objetivos, su diseño sería realmente muy bueno. Algunos objetivos contradicen otros objetivos, pero
belleza. Sólo pienso en cómo

resolver el problema. Pero


ese es el desafío del diseño: crear un buen conjunto de compensaciones a partir de objetivos en
cuando he terminado, si la competencia. Algunas características de la calidad del diseño también son características de un buen
solución no es hermosa, sé que
programa: fiabilidad, rendimiento, etc. Otras son características internas del diseño.
está mal.

—R. Buckminster Fuller

Referencia cruzadaEstas Aquí hay una lista de características de diseño interno:


Las características están relacionadas con

los atributos generales de calidad del


Complejidad mínimaEl objetivo principal del diseño debe ser minimizar la complejidad por todas las
software. Para obtener detalles sobre los
razones que acabamos de describir. Evite hacer diseños “inteligentes”. Los diseños inteligentes
atributos generales, consulte la Secció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.

Facilidad de mantenimientoFacilidad de mantenimiento significa diseñar para el programador de


mantenimiento. Imagine continuamente las preguntas que un programador de mantenimiento haría
sobre el código que está escribiendo. Piense en el programador de mantenimiento como su audiencia y
luego diseñe el sistema para que se explique por sí mismo.

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.

ExtensibilidadLa extensibilidad significa que puede mejorar un sistema sin causar


violencia a la estructura subyacente. Puede cambiar una parte de un sistema sin
afectar otras partes. Los cambios más probables causan al sistema el menor trauma.

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) .

PortabilidadLa portabilidad significa diseñar el sistema para que pueda moverlo


fácilmente a otro entorno.

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?"

EstratificaciónLa estratificación significa tratar de mantener los niveles de descomposición


estratificados para que pueda ver el sistema en cualquier nivel único y obtener una vista consistente.
Diseñe el sistema para que pueda verlo en un nivel sin sumergirse en otros niveles.

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

3 División en clases dentro de paquetes

4 División en datos y rutinas dentro de las clases.

5Diseño de rutinas internas

Figura 5-2Los 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).

Nivel 1: Sistema de Software

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.

Interfaz de usuario Gráficos

Solicitud
Almacenamiento de datos
Clases de nivel

Negocio Nivel de Empresa


Normas Instrumentos

Figura 5-3Un ejemplo de un sistema con seis subsistemas.

Interfaz de usuario Gráficos

Solicitud
Almacenamiento de datos
Clases de nivel

Negocio Nivel de Empresa


Normas Instrumentos

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

84 Capítulo 5: Diseño en la Construcción

Como puede ver, cada subsistema termina comunicándose directamente con todos los demás
subsistemas, lo que plantea algunas preguntas importantes:

- ¿Cuántas partes diferentes del sistema necesita comprender un desarrollador al


menos un poco para cambiar algo en el subsistema de gráficos?

- ¿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

usuario de línea de comandos con fines de prueba?

- ¿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.

Interfaz de usuario Gráficos

Solicitud
Almacenamiento de datos
Clases de nivel

Negocio Nivel de Empresa


Normas Instrumentos

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.

En programas grandes y familias de programas, el diseño a nivel de subsistema marca la diferencia.


Si cree que su programa es lo suficientemente pequeño como para omitir el diseño a nivel de
subsistema, al menos tome la decisión de omitir ese nivel de diseño de manera consciente.

Subsistemas ComunesAlgunos tipos de subsistemas aparecen una y otra vez en diferentes


sistemas. Estos son algunos de los sospechosos habituales.

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,

consulte el Capítulo 18, "Métodos


IRS sobre la cantidad de retenciones permitidas y la tasa impositiva estimada. Las reglas adicionales para un
controlados por tablas". sistema de nómina pueden provenir de un contrato sindical que especifique tarifas de horas extras, pago de
vacaciones y feriados, etc. Si está escribiendo un programa para cotizar tarifas de seguros de automóviles,
las reglas pueden provenir de las regulaciones gubernamentales sobre las coberturas de responsabilidad
requeridas, las tablas de tasas actuariales o las restricciones de suscripción.

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

Nivel 3: División en Clases


Otras lecturasPara una buena El diseño en este nivel incluye la identificación de todas las clases en el sistema. Por ejemplo, un
discusión de la base de datos
subsistema de interfaz de base de datos podría dividirse aún más en clases de acceso a datos y clases
diseño, verTécnicas de bases de
datos ágiles(Ambler 2003).
de marco de persistencia, así como metadatos de base de datos. La Figura 5-2, Nivel 3, muestra cómo
uno de los subsistemas del Nivel 2 puede dividirse en clases, e implica que los otros tres subsistemas
que se muestran en el Nivel 2 también se descomponen en clases.

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.

Nivel 4: División en Rutinas


El diseño en este nivel incluye dividir cada clase en rutinas. La interfaz de clase definida en el
Nivel 3 definirá algunas de las rutinas. Diseño en el Nivel 4 detallará las rutinas privadas de la
clase. Cuando examina los detalles de las rutinas dentro de una clase, puede ver que muchas
rutinas son cajas simples, pero algunas están compuestas de rutinas organizadas
jerárquicamente, que requieren aún más diseño.

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

Nivel 5: Diseño de Rutinas Internas

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,

"Rutinas de alta calidad", y el Capítulo


trabaja en una rutina individual. El diseño consta de actividades como escribir pseudocódigo,
8, "Programación defensiva". buscar algoritmos en libros de referencia, decidir cómo organizar los párrafos del código en
una rutina y escribir código en lenguaje de programación. Este nivel de diseño siempre se
hace, aunque a veces se hace de manera inconsciente y deficiente en lugar de consciente y
bien. En la Figura 5-2, el diseño en este nivel está marcado con un5.

5.3 Elementos básicos del diseño: heurística


A los desarrolladores de software les suelen gustar nuestras respuestas cortadas y secas: "Haz A, B y C, y X,
Y, Z seguirán cada vez". Nos enorgullecemos de aprender conjuntos arcanos de pasos que producen los
efectos deseados, y nos molestamos cuando las instrucciones no funcionan como se anuncia. Este deseo de
comportamiento determinista es muy apropiado para la programación informática detallada, donde ese tipo
de atención estricta a los detalles hace o deshace un programa. Pero el diseño de software es una historia
muy diferente.

Debido a que el diseño no es determinista, la aplicación hábil de un conjunto efectivo de heurísticas


es la actividad central en un buen diseño de software. Las siguientes subsecciones describen una
serie de heurísticas: formas de pensar en un diseño que en algún momento producen buenas
perspectivas de diseño. Puede pensar en las heurísticas como las guías para las pruebas en "ensayo y
error". Seguro que te has topado con alguno de ellos antes. En consecuencia, las siguientes
subsecciones describen cada una de las heurísticas en términos del Imperativo Técnico Primario del
Software: gestionar la complejidad.

Encuentra objetos del mundo real

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.

Los pasos para diseñar con objetos son

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.

- Defina la interfaz pública de cada objeto.


88 Capítulo 5: Diseño en la Construcción

Estos pasos no se realizan necesariamente en orden y, a menudo, se repiten. La iteración es


importante. Cada uno de estos pasos se resume a continuación.

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

nombre Dirección de Envio


título saldo de la cuenta
tarifa de facturación cantidad de facturación actual

ObtenerHorasPorMes() IngresarPago()
... ...

1 facturaciónEmpleado 1 1clienteafacturar
clienteafacturar

facturas
* * *
Tarjeta de tiempo Factura

horas Fecha de pago

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

(ocultar información)" en la Sección 5.3.


mantenerse en privado. Esta decisión debe tomarse tanto para los datos como para los métodos.

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.

Formar abstracciones consistentes

La abstracción es la capacidad de comprometerse con un concepto ignorando de manera segura


algunos de sus detalles, manejando diferentes detalles en diferentes niveles. Cada vez que trabaja
con un agregado, está trabajando con una abstracción. Si te refieres a un objeto como una "casa" en
lugar de una combinación de vidrio, madera y clavos, estás haciendo una abstracción. Si te refieres a
una colección de casas como un "pueblo", estás haciendo otra abstracción.

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.

Desde el punto de vista de la complejidad, el principal beneficio de la abstracción es que te permite


ignorar detalles irrelevantes. La mayoría de los objetos del mundo real ya son abstracciones de algún
tipo. Como se acaba de mencionar, una casa es una abstracción de ventanas, puertas,
revestimientos, cableado, plomería, aislamiento y una forma particular de organizarlos. Una puerta
es a su vez una abstracción de una disposición particular de una pieza rectangular de material con
bisagras y un picaporte. Y el pomo de la puerta es una abstracción de una formación particular de
latón, níquel, hierro o acero.
90 Capítulo 5: Diseño en la Construcción

La gente usa tratar con fibras de madera individuales,


barniz m usó su puerta de entrada, lo sugiere la
apenas mamá Figura 5-7, la abstracción es un mundo
Una gran parte real.

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

abstracción" en la Sección 6.2.


administrar. Cuando los programadores no pueden proporcionar abstracciones de programación más
grandes, el sistema en sí mismo a veces no logra pasar por la puerta principal.

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.

Encapsular detalles de implementación


La encapsulación continúa donde termina la abstracción. La abstracción dice: "Se te
permite mirar un objeto con un alto nivel de detalle". La encapsulación dice: "Además, no
se le permite mirar un objeto en ningún otro nivel de detalle".

Continuando con la analogía de los materiales de la vivienda: la encapsulación es una forma de


decir que puedes mirar el exterior de la casa pero no puedes acercarte lo suficiente para
distinguir los detalles de la puerta. Se le permite saber que hay una puerta y se le permite
saber si la puerta está abierta o cerrada, pero no se le permite saber si la puerta está hecha de
madera, fibra de vidrio, acero o algún otro material. y ciertamente no está permitido mirar
cada fibra de madera individual.

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!

Heredar: cuando la herencia simplifica el diseño


Al diseñar un sistema de software, a menudo encontrará objetos que son muy parecidos a otros
objetos, excepto por algunas diferencias. En un sistema de contabilidad, por ejemplo, puede tener
empleados de tiempo completo y de medio tiempo. La mayoría de los datos asociados con ambos
tipos de empleados son los mismos, pero algunos son diferentes. En la programación orientada a
objetos, puede definir un tipo general de empleado y luego definir empleados a tiempo completo
como empleados generales, excepto por algunas diferencias, y empleados a tiempo parcial también
como empleados generales, excepto por algunas diferencias. Cuando una operación sobre un
empleado no depende del tipo de empleado, la operación se maneja como si el empleado fuera solo
un empleado general. Cuando la operación depende de si el empleado es de tiempo completo o de
medio tiempo, la operación se maneja de manera diferente.

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.

El beneficio de la herencia es que funciona sinérgicamente con la noción de abstracción. La


abstracción trata con objetos en diferentes niveles de detalle. Recuerde la puerta que era una
colección de ciertos tipos de moléculas en un nivel, una colección de fibras de madera en el siguiente
y algo que mantiene a los ladrones fuera de su casa en el siguiente nivel. La madera tiene ciertas
propiedades, por ejemplo, puede cortarla con una sierra o pegarla con pegamento para madera, y
las tejas de dos por cuatro o de cedro tienen las propiedades generales de la madera, así como
algunas propiedades específicas propias.

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

más información, consulte “Herencia (“es una” relación)” en la Sección 6.3.

Ocultar secretos (ocultar información)


La ocultación de información es parte de la base tanto del diseño estructurado como del diseño
orientado a objetos. En el diseño estructurado, la noción de "cajas negras" proviene de la ocultación
de información. En el diseño orientado a objetos da lugar a los conceptos de encapsulación y
modularidad y se asocia al concepto de abstracción. La ocultación de información es una de las ideas
seminales en el desarrollo de software, por lo que esta subsección la explora en profundidad.

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).

La ocultación de información es una heurística particularmente poderosa para el imperativo técnico


primario del software porque, comenzando con su nombre y a lo largo de sus detalles, enfatiza
ocultando la complejidad.

Secretos y derecho a la privacidad

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.

Un ejemplo de ocultación de información

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

Dos categorías de secretos

Los secretos en la ocultación de información se dividen en dos campos generales:

- 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.

Barreras a la ocultación de información

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.

Otro ejemplo de distribución excesiva de información es la interacción intercalada con usuarios


humanos en todo un sistema. Si el modo de interacción cambia, digamos, de una interfaz GUI
a una interfaz de línea de comandos, prácticamente todo el código deberá modificarse. Es
mejor concentrar la interacción del usuario en una sola clase, paquete o subsistema que puede
cambiar sin afectar todo el sistema.

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,

consulte “Uso de rutinas de acceso


datos globales directamente, la información sobre la implementación del elemento de datos, como el
en lugar de datos globales” en hecho de que es una matriz y tiene un máximo de 1000 elementos, se distribuirá por todo el
la Sección 13.3.
programa. Si el programa usa los datos solo a través de rutinas de acceso, solo las rutinas de acceso
conocerán los detalles de implementación.

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

25, "Estrategias de ajuste de código" y


No necesita preocuparse en ninguno de los dos niveles. A nivel arquitectónico, la preocupación es
el Capítulo 26, "Técnicas de ajuste de innecesaria porque diseñar un sistema para ocultar información no entra en conflicto con diseñarlo
código".
para el rendimiento. Si tiene en cuenta tanto la ocultación de la información como el rendimiento,
puede lograr ambos objetivos.

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.

Valor de la ocultación de información

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

pensar en la ocultación de información inspira y promueve decisiones de diseño que


pensar en objetos no.

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.

Adquiera el hábito de preguntar "¿Qué debo ocultar?" Se sorprenderá de la cantidad de problemas de


diseño difíciles que se disuelven ante sus ojos.

PUNTO CLAVE

Identificar áreas que probablemente cambien

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.

2. Separe los elementos que probablemente cambien.Compartimente cada componente volátil


identificado en el paso 1 en su propia clase o en una clase con otros componentes volátiles
que probablemente cambien al mismo tiempo.

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

Aquí hay algunas áreas que probablemente cambien:

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.

Para obtener más información, consulte el


compañía de seguros cambia sus tablas de tarifas. Si sigue el principio de ocultar información, la
Capítulo 18, “Métodos controlados por lógica basada en estas reglas no se esparcirá por todo su programa. La lógica permanecerá oculta
tablas”.
en un único rincón oscuro del sistema hasta que sea necesario cambiarla.

Dependencias de hardwareLos ejemplos de dependencias de hardware incluyen interfaces para


pantallas, impresoras, teclados, ratones, unidades de disco, instalaciones de sonido y dispositivos de
comunicación. Aísle las dependencias de hardware en su propio subsistema o clase. Aislar dichas
dependencias ayuda cuando mueve el programa a un nuevo entorno de hardware. También ayuda
inicialmente cuando está desarrollando un programa para hardware volátil. Puede escribir software
que simule la interacción con hardware específico, hacer que el subsistema de interfaz de hardware
use el simulador siempre que el hardware sea inestable o no esté disponible, y luego desconecte el
subsistema de interfaz de hardware del simulador y conecte el subsistema al hardware cuando esté
listo para usar.

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.

Características del lenguaje no estándarLa mayoría de las implementaciones de lenguaje


contienen extensiones prácticas no estándar. El uso de las extensiones es una espada de doble filo
porque es posible que no estén disponibles en un entorno diferente, ya sea que el entorno diferente
sea un hardware diferente, la implementación del idioma de un proveedor diferente o una nueva
versión del idioma del mismo proveedor.

Si usa extensiones no estándar para su lenguaje de programación, oculte esas extensiones en


una clase propia para que pueda reemplazarlas con su propio código cuando se mueva a un
entorno diferente. Del mismo modo, si utiliza rutinas de biblioteca que no están disponibles en
todos los entornos, oculte las rutinas de biblioteca reales detrás de una interfaz que funcione
igual de bien en otro entorno.

Á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.

Puede agregar al menos dos niveles de flexibilidad y legibilidad a su uso de variables de


estado:

- 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.

- Use rutinas de acceso en lugar de verificar la variable directamente. Al verificar la rutina


de acceso en lugar de la variable, permite la posibilidad de una detección de estado más
sofisticada. Por ejemplo, si quisiera verificar combinaciones de una variable de estado
de error y una variable de estado de función actual, sería fácil de hacer si la prueba
estuviera oculta en una rutina y difícil de hacer si fuera una prueba complicada.
codificado a lo largo del programa.

Restricciones de tamaño de datosCuando declaras una matriz de tamaño100,estás exponiendo


información al mundo que el mundo no necesita ver. ¡Defiende tu derecho a la privacidad! Ocultar
información no siempre es tan complicado como toda una clase. A veces es tan simple como usar una
constante con nombre comoMAX_EMPLEADOSocultar un100.

Anticipar diferentes grados de cambio


Referencia cruzadaEl enfoque de Al pensar en posibles cambios en un sistema, diseñe el sistema de modo que el efecto o alcance del
esta sección para anticipar el
cambio sea proporcional a la probabilidad de que ocurra. Si es probable que se produzca un cambio,
cambio no implica diseñar o

codificar con anticipación. Para


asegúrese de que el sistema pueda adaptarse fácilmente. Solo se debe permitir que los cambios
una discusión de esas prácticas, extremadamente improbables tengan consecuencias drásticas para más de una clase en un sistema.
consulte "Un programa contiene
Los buenos diseñadores también tienen en cuenta el costo de anticipar el cambio. Si un cambio no es
código que parece que podría ser

necesario algún día" en la Sección


muy probable pero fácil de planificar, debe pensar más en anticiparlo que si no es muy probable y es
24.2. difícil de planificar.

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

Mantenga el acoplamiento suelto

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.

VisibilidadLa visibilidad se refiere a la prominencia de la conexión entre dos módulos.


Programar no es como estar en la CIA; no obtienes crédito por ser astuto. Es más como
publicidad; obtienes mucho crédito por hacer que tus conexiones sean lo más evidentes
posible. Pasar datos en una lista de parámetros es hacer una conexión obvia y, por lo tanto, es
bueno. Modificar datos globales para que otro módulo pueda usar esos datos es una conexión
engañosa y, por lo tanto, es mala. La documentación de la conexión de datos globales la hace
más obvia y es un poco mejor.

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.

Para el tercer módulo a utilizarLookupVacationBenefit(), tiene que saber sobre el Empleado


clase. Podría simular unempleadoobjeto con sólo dos campos, pero eso requeriría
conocimiento interno deLookupVacationBenefit(), es decir, que esos son los únicos campos
que utiliza. Tal solución sería una chapuza, y una fea. La segunda opción sería modificar
LookupVacationBenefit()para que tomara la fecha de contratación y la clasificación del trabajo
en lugar deempleado. En cualquier caso, el módulo original resulta ser mucho menos flexible
de lo que parecía al principio.

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

Estos son los tipos de acoplamiento más comunes que encontrará.

Acoplamiento de parámetros de datos simplesDos módulos están acoplados a parámetros de datos


simples si todos los datos que se pasan entre ellos son de tipos de datos primitivos y todos los datos se
pasan a través de listas de parámetros. Este tipo de acoplamiento es normal y aceptable.

Acoplamiento de objeto simpleUn módulo es un objeto simple acoplado a un objeto si instancia ese
objeto. Este tipo de acoplamiento está bien.

Acoplamiento objeto-parámetroDos módulos están acoplados objeto-parámetro entre sí si


Objeto1requiereObjeto2para pasarle unObjeto3. Este tipo de acoplamiento es más estrecho
queObjeto1requiriendoObjeto2para pasarle solo tipos de datos primitivos porque requiere
Objeto2saber sobreObjeto3.
102 Capítulo 5: Diseño en la Construcción

Acoplamiento semánticoEl tipo de acoplamiento más insidioso ocurre cuando un módulo


hace uso no de algún elemento sintáctico de otro módulo sino de algún conocimiento
semántico del funcionamiento interno de otro módulo. Aquí hay unos ejemplos:

- 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.

- Módulo 1pasaObjetoaMódulo2. PorqueMódulo 1saber esoMódulo2utiliza sólo tres de


ObjetoLos siete métodos de 's, se inicializaObjetosolo parcialmente, con los datos
específicos que necesitan esos tres métodos.

- Módulo 1pasaBaseObjetoaMódulo2. PorqueMódulo2saber esoMódulo 1realmente lo


está pasandoObjetoDerivado, arrojaBaseObjetoaObjetoDerivadoy llama a métodos que
son específicos deObjetoDerivado.

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

Busque patrones de diseño comunes


cc2e.com/0585 Los patrones de diseño proporcionan los núcleos de las soluciones listas para usar que se pueden usar para
resolver muchos de los problemas más comunes del software. Algunos problemas de software requieren
soluciones que se derivan de los primeros principios. Pero la mayoría de los problemas son similares a
problemas del pasado, y se pueden resolver usando soluciones o patrones similares. Los patrones comunes
incluyen adaptador, puente, decorador, fachada, método de fábrica, observador, singleton, estrategia y
método de plantilla. El libroPatrones de diseñopor Erich Gamma, Richard Helm, Ralph Johnson y John
Vlissides (1995) es la descripción definitiva de los patrones de diseño.

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

solución de diseño preconstruida que crear una solución novedosa.

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

Los patrones agilizan la comunicación al mover el cuadro de diálogo de diseño a un nivel


superiorAdemás de su beneficio de gestión de la complejidad, los patrones de diseño pueden
acelerar las discusiones de diseño al permitir que los diseñadores piensen y discutan en un
mayor nivel de granularidad. Si dice "No puedo decidir si debo usar un Método de creador o de
fábrica en esta situación", ha comunicado mucho con solo unas pocas palabras, siempre que
usted y su oyente estén familiarizados con esos patrones. . Imagínese cuánto tiempo le llevaría
sumergirse en los detalles del código para un patrón Creator y el código para un patrón
Factory Method y luego comparar y contrastar los dos enfoques.

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.

Tabla 5-1 Patrones de diseño populares

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.

Adaptador Convierte la interfaz de una clase a una interfaz diferente.


Puente Construye una interfaz y una implementación de tal manera que
cualquiera puede variar sin que la otra varíe.
Compuesto Consiste en un objeto que contiene objetos adicionales de su propio tipo para
que el código del cliente pueda interactuar con el objeto de nivel superior y
no preocuparse por todos los objetos detallados.

Decorador Adjunta responsabilidades a un objeto de forma dinámica, sin crear subclases


específicas para cada posible configuración de responsabilidades.
Fachada Proporciona una interfaz consistente al código que de otro modo no
ofrecería una interfaz consistente.
Método de fábrica Crea instancias de clases derivadas de una clase base específica sin necesidad de realizar
un seguimiento de las clases derivadas individuales en cualquier lugar que no sea el
método de fábrica.

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í.

Método de plantilla Define la estructura de un algoritmo pero deja parte de la


implementación detallada a las subclases.

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.

Apunta a una fuerte cohesión

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.

Formalizar Contratos de Clase

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

verificar condiciones previas y posteriores"


promete proporcionar datos x, y y z y promete que tendrán las características a, b y c, prometo
en la Sección 8.2. realizar las operaciones 1, 2 y 3 dentro de las restricciones 8, 9 y 10.” Las promesas que los clientes
de la clase le hacen a la clase generalmente se denominan "condiciones previas", y las promesas que
el objeto hace a sus clientes se denominan "condiciones posteriores".

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.

Diseño para prueba

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

El profesor de ingeniería civil Henry Petroski escribió un libro interesante,Paradigmas de diseño:


historias de casos de error y juicio en ingeniería(Petroski 1994), que relata la historia de fallas en el
diseño de puentes. Petroski argumenta que se han producido muchas fallas de puentes
espectaculares debido a que se centró en los éxitos anteriores y no se consideraron adecuadamente
los posibles modos de falla. Concluye que fallas como la del puente de Tacoma Narrows podrían
haberse evitado si los diseñadores hubieran considerado cuidadosamente las formas en que el
puente podría fallar y no solo copiado los atributos de otros diseños exitosos.
5.3 Elementos básicos del diseño: heurística 107

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.

Elija el tiempo de enlace conscientemente

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?

Hacer puntos centrales de control

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.

El beneficio de la complejidad reducida es que cuantos menos lugares tenga para


buscar algo, más fácil y seguro será cambiar.

Considere usar la fuerza bruta

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.

Mantenga su diseño modular

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.

El concepto de modularidad está relacionado con la ocultación de información, la encapsulación y otras


heurísticas de diseño. Pero a veces, pensar en cómo ensamblar un sistema a partir de un conjunto de cajas
negras proporciona información que la ocultación y el encapsulado de información no brindan, por lo que
vale la pena tener el concepto en su bolsillo trasero.

Resumen de heurísticas de diseño


Lo que es más alarmante, el He aquí un resumen de las principales heurísticas de diseño:
mismo programador es bastante

capaz de hacer la misma tarea - Encuentra objetos del mundo real


por sí mismo de dos o tres

maneras, a veces sin control. - Formar abstracciones consistentes


conscientemente, pero muy a menudo

simplemente para variar, o para - Encapsular detalles de implementación


proporcionar una variación elegante.

—AR Brown y WA - Heredar cuando sea posible


Sampson
- Ocultar secretos (ocultar información)

- Identificar áreas que probablemente cambien

- Mantenga el acoplamiento suelto

- Busque patrones de diseño comunes

Las siguientes heurísticas a veces también son útiles:

- Apunta a una fuerte cohesión

- Crear jerarquías

- Formalizar Contratos de Clase

- Asignar responsabilidades

- Diseño para prueba

- Evite el fracaso

- Elija el tiempo de enlace conscientemente

- Hacer puntos centrales de control

- Considere usar la fuerza bruta

- dibujar un diagrama

- Mantenga su diseño modular


5.3 Elementos básicos del diseño: heurística 109

Directrices para el uso de la heurística

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).

cc2e.com/0592 1. Entendiendo el Problema.Tienes quecomprenderel problema.


¿Qué es lo desconocido? ¿Cuáles son los datos? ¿Cuál es la condición?¿Es posible
satisfacer la condición? ¿Es la condición suficiente para determinar la incógnita? ¿O es
insuficiente? ¿O redundante? ¿O contradictorio?
Dibujar una figura. Introduzca la notación adecuada. Separe las diversas partes
de la condición. ¿Puedes escribirlos?

2. Elaboración de un plan.Encuentra la conexión entre los datos y lo desconocido. Es posible


que se vea obligado a considerar problemas auxiliares si no puede encontrar una conexión
intermedia. Eventualmente deberías llegar a unplande la solución

¿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?

3. Ejecución del Plan.Realizartu plan.


Llevando a cabo su plan de solución,revisa cada paso. ¿Puedes ver claramente que el
paso es correcto? ¿Puedes probar que es correcto?

4. Mirando hacia atrás.Examinarla solución.

Puedecomprobar el resultado? ¿Puedes comprobar el argumento? ¿Puede obtener el resultado de


otra manera? ¿Puedes verlo de un vistazo?
¿Puedes usar el resultado, o el método, para algún otro 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) .

5.4 Prácticas de diseño


La sección anterior se centró en la heurística relacionada con los atributos de diseño: cómo desea
que se vea el diseño completo. Esta sección describepráctica de diseñoheurística, pasos que puede
tomar que a menudo producen buenos resultados.

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

información sobre esto, consulte el


intento que pueden mejorar su diseño general. Después de probar sin éxito mil materiales
Capítulo 24, "Refactorización". diferentes para el filamento de una bombilla, se le preguntó a Thomas Edison si sentía que
había perdido el tiempo ya que no había descubierto nada. “Tonterías”, se supone que
respondió Edison. “He descubierto mil cosas que no funcionan”. En muchos casos, resolver el
problema con un enfoque producirá conocimientos que le permitirán resolver el problema
utilizando otro enfoque que es aún mejor.

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!

El refinamiento incremental es una poderosa herramienta para gestionar la complejidad. Como


recomienda Polya en la resolución de problemas matemáticos, comprenda el problema, diseñe un
plan, lleve a cabo el plan y luegomirar atráspara ver cómo lo hiciste (Polya 1957).

Enfoques de diseño de arriba hacia abajo y de abajo hacia arriba

"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

Argumento a favor de arriba hacia abajo

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.

El proceso divide y vencerás es iterativo en un par de sentidos. Primero, es iterativo porque


normalmente no se detiene después de un nivel de descomposición. Sigues avanzando por
varios niveles. En segundo lugar, es iterativo porque normalmente no te conformas con tu
primer intento. Usted descompone un programa de una manera. En varios puntos de la
descomposición, tendrá opciones sobre cómo dividir los subsistemas, diseñar el árbol de
herencia y formar composiciones de objetos. Tú eliges y ves qué pasa. Luego empiezas de
nuevo y lo descompones de otra manera y ves si eso funciona mejor. Después de varios
intentos, tendrá una buena idea de qué funcionará y por qué.

¿Hasta qué punto se descompone un programa? Continúe descomponiendo hasta que


parezca que sería más fácil codificar el siguiente nivel que descomponerlo. Trabaja hasta que
te impacientes un poco por lo obvio y fácil que parece el diseño. En ese momento, ya está. Si
no está claro, trabaje un poco más. Si la solución es un poco complicada para usted ahora,
será un oso para cualquiera que trabaje en ella más tarde.

Argumento a favor de abajo hacia arriba

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:

- Pregúntese qué sabe que debe hacer el sistema.

- Identifique objetos y responsabilidades concretas a partir de esa pregunta.

- Identifique objetos comunes y agrúpelos utilizando la organización del subsistema, los


paquetes, la composición dentro de los objetos o la herencia, según corresponda.

- 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

Sin argumentos, de verdad

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

114 Capítulo 5: Diseño en la Construcción

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.

La creación de prototipos también funciona mal cuando la pregunta de diseño no esespecíficosuficiente.


Una pregunta de diseño como "¿Funcionará este marco de base de datos?" no proporciona suficiente
dirección para la creación de prototipos. Una pregunta de diseño como "¿Este marco de base de datos
admitirá 1000 transacciones por segundo bajo los supuestos X, Y y Z?" proporciona una base más sólida
para la creación de prototipos.

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.

- Le pides ayuda a alguien fuera de tu empresa: envía preguntas a un foro o grupo de


noticias especializado.

Si el objetivo es el aseguramiento de la calidad, tiendo a recomendar la práctica de revisión más


estructurada, las inspecciones formales, por las razones descritas en el Capítulo 21. Pero si el objetivo
es fomentar la creatividad y aumentar el número de alternativas de diseño generadas, no solo
encontrar errores, los enfoques menos estructurados funcionan mejor. Una vez que se haya decidido
por un diseño específico, podría ser apropiado cambiar a una inspección más formal, según la
naturaleza de su proyecto.

¿Cuánto diseño es suficiente?


Intentamos resolver el problema A veces, solo se traza el boceto más básico de una arquitectura antes de que comience la
acelerando el proceso de diseño
codificación. Otras veces, los equipos crean diseños con tal nivel de detalle que la codificación se
para que quede suficiente tiempo

al final del proyecto para


convierte en un ejercicio principalmente mecánico. ¿Cuánto diseño debe hacer antes de comenzar a
descubrir los errores que se codificar?
cometieron porque nos

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.

Tabla 5-2 Formalidad del diseño y nivel de detalle necesario

Nivel de detalle necesario


en el diseño antes Documentación
Factor Construcción Formalidad

El equipo de diseño/construcción Detalle bajo Formalidad baja


tiene una gran experiencia en el
área de aplicaciones.

El equipo de diseño/construcción Detalle medio Formalidad Media


tiene una gran experiencia pero
no tiene experiencia en el área
de aplicaciones.

El equipo de diseño/construcción Detalle medio a alto Formalidad Baja-Media


no tiene experiencia.

El equipo de diseño/construcción Detalle medio —


tiene de moderado a alto
Rotación.
La aplicación es Alto detalle Alta Formalidad
seguridad crítica.

La aplicación es Detalle medio Formalidad media-alta


misión crítica.
El proyecto es pequeño. Detalle bajo Formalidad baja
El proyecto es grande. Detalle medio Formalidad Media
Se espera que el software Detalle bajo Formalidad baja
tenga una vida útil corta
(semanas o meses).

Se espera que el software Detalle medio Formalidad Media


tenga una larga vida útil
(meses o años).

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.

Capturar su trabajo de diseño


cc2e.com/0506 El enfoque tradicional para capturar el trabajo de diseño es escribir los diseños en un documento de
diseño formal. Sin embargo, puede capturar diseños de numerosas formas alternativas que
funcionan bien en proyectos pequeños, proyectos informales o proyectos que necesitan una forma
ligera de registrar un diseño:

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

un proceso que nos permita diseñar


enfoque con un extractor de documentación como JavaDoc, esto asegura que la documentación de diseño
software de forma perfectamente estará fácilmente disponible para un programador que trabaja en una sección de código, y mejora la
racional. La buena noticia es que
posibilidad de que los programadores mantengan la documentación de diseño razonablemente actualizada.
podemos falsificarlo.

—David Parnas y Paul


Clements
Capture discusiones y decisiones de diseño en un wikiTenga sus discusiones de diseño por escrito,
en un proyecto Wiki (es decir, una colección de páginas web que cualquier persona en su proyecto
puede editar fácilmente usando un navegador web). Esto capturará sus discusiones y decisiones de
diseño automáticamente, aunque con la sobrecarga adicional de escribir en lugar de hablar. También
puede usar Wiki para capturar imágenes digitales para complementar la discusión de texto, enlaces a
sitios web que respaldan la decisión de diseño, documentos técnicos y otros materiales. Esta técnica
es especialmente útil si su equipo de desarrollo está distribuido geográficamente.

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.

5.5 Comentarios sobre metodologías populares


La historia del diseño en software ha estado marcada por defensores fanáticos de enfoques de
diseño tremendamente conflictivos. Cuando publiqué la primera edición deCódigo completoa
principios de la década de 1990, los fanáticos del diseño abogaban por salpicar cada diseñoiy
cruzando cada diseñotantes de comenzar a codificar. Esa recomendación no tenía ningún sentido.
Recursos adicionales 119

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

energía que nos hace sentir culpables


que no hagas ningún diseño antes de comenzar a programar!”
a todos. Nunca podremos estar lo

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

haber aprendido Basic a una edad


¿Cómo saber cuánto es suficiente? Esa es una decisión de juicio, y nadie puede hacer esa decisión a
impresionable. Pero mi apuesta es

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.

Diseño de Software, General


Weisfield, Matt.El proceso de pensamiento orientado a objetos, 2ª ed. SAMS, 2004. Este es un
libro accesible que introduce la programación orientada a objetos. Si ya está familiarizado con
la programación orientada a objetos, probablemente querrá un libro más avanzado, pero si
solo se está iniciando en la orientación a objetos, este libro presenta conceptos fundamentales
orientados a objetos, incluidos objetos, clases, interfaces, herencia, polimorfismo, sobrecarga,
clases abstractas, agregación y asociación, constructores/destructores, excepciones y otros.

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

MEYER, Bertrand.Construcción de software orientada a objetos, 2ª ed. New York, NY:


Prentice Hall PTR, 1997. Meyer presenta una enérgica defensa de la programación
orientada a objetos.

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.

Larman, Craig.Aplicación de UML y patrones: una introducción al análisis y diseño orientado a


objetos y al proceso unificado, 2ª ed. Englewood Cliffs, NJ: Prentice Hall, 2001. Este libro es una
introducción popular al diseño orientado a objetos en el contexto del Proceso Unificado.
También analiza el análisis orientado a objetos.

Teoría del diseño de software

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

siendo los mejores recursos sobre el ocultamiento de información.

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. "Diseño de software para facilitar la extensión y la contracción".


Transacciones IEEE sobre ingeniería de softwareSE-5, núm. 2 (marzo de 1979): 128-38.

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.

Shalloway, Alan y James R. Trott.Patrones de diseño explicados. Boston, MA: Addison-Wesley,


2002. Este libro contiene una introducción fácil de leer a los patrones de diseño.
Recursos adicionales 121

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.

Michalewicz, Zbigniew y David B. Fogel.Cómo resolverlo: heurística moderna. Berlín: Springer-


Verlag, 2000. Este es un tratamiento actualizado del libro de Polya que es un poco más fácil de
leer y que también contiene algunos ejemplos no matemáticos.

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.

Petrosky, Henry.Paradigmas de diseño: historias de casos de error y juicio en ingeniería. Cambridge:


Cambridge University Press, 1994. Este libro se basa en gran medida en el campo de la ingeniería civil
(especialmente en el diseño de puentes) para explicar su argumento principal de que el éxito del diseño
depende al menos tanto del aprendizaje de los fracasos pasados como de los éxitos pasados.
122 Capítulo 5: Diseño en la Construcción

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.

Norma IEEE 1471-2000. Práctica recomendada para la descripción arquitectónica de sistemas


intensivos en software. Los Alamitos, CA: IEEE Computer Society Press. Este documento es la guía
IEEE-ANSI para crear especificaciones de arquitectura de software.

cc2e.com/0527 LISTA DE VERIFICACIÓN: Diseño en la Construcción

Prácticas de diseño
- ¿Ha iterado, seleccionando el mejor de varios intentos en lugar del primer
intento?

- ¿Ha intentado descomponer el sistema en varias formas diferentes para


ver cuál funciona mejor?

- ¿Ha abordado el problema de diseño tanto de arriba hacia abajo como de


abajo hacia arriba?

- ¿Ha creado prototipos de partes riesgosas o desconocidas del sistema, creando la


cantidad mínima absoluta de código desechable necesario para responder preguntas
específicas?

- ¿Su diseño ha sido revisado, formal o informalmente, por otros?

- ¿Ha llevado el diseño hasta el punto de que su implementación parece


obvia?

- ¿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

- ¿El diseño aborda adecuadamente los problemas que se identificaron y


postergaron a nivel arquitectónico?

- ¿El diseño está estratificado en capas?

- ¿Está satisfecho con la forma en que se ha descompuesto el programa en


subsistemas, paquetes y clases?

- ¿Está satisfecho con la forma en que se han descompuesto las clases en


rutinas?

- ¿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?

- ¿El programa será fácil de mantener?

- ¿El diseño es delgado? ¿Son todas sus partes estrictamente necesarias?

- ¿El diseño utiliza técnicas estándar y evita elementos exóticos y difíciles


de entender?

- En general, ¿ayuda el diseño a minimizar tanto la complejidad accidental como


la esencial?

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.

- La simplicidad se logra de dos maneras generales: minimizando la cantidad de complejidad esencial


con la que el cerebro de cualquier persona tiene que lidiar en un momento dado, y evitando que la
complejidad accidental prolifere innecesariamente.

- El diseño es heurístico. La adhesión dogmática a una sola metodología daña la


creatividad y daña sus programas.

- El buen diseño es iterativo; Cuantas más posibilidades de diseño pruebe, mejor será
su diseño final.

- La ocultación de información es un concepto particularmente valioso. Preguntar "¿Qué debo ocultar?"

resuelve muchos problemas de diseño difíciles.

- 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

- 6.1 Fundamentos de clase: tipos de datos abstractos (ADT): página 126

- 6.2 Interfaces de buena clase: página 133

- 6.3 Problemas de diseño e implementación: página 143

- 6.4 Razones para crear una clase: página 152

- 6.5 Problemas específicos del idioma: página 156

- 6.6 Más allá de las clases: Paquetes: página 156

Temas relacionados

- Diseño en construcción: Capítulo 5

- Arquitectura de software: Sección 3.5

- Rutinas de alta calidad: Capítulo 7

- El proceso de programación del pseudocódigo: Capítulo 9

- Refactorización: Capítulo 24

En los albores de la informática, los programadores pensaban en la programación en términos de


declaraciones. Durante las décadas de 1970 y 1980, los programadores comenzaron a pensar en los
programas en términos de rutinas. En el siglo XXI, los programadores piensan en la programación
en términos de clases.

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

6.1 Fundamentos de clase: tipos de datos abstractos (ADT)


Un tipo de datos abstracto es una colección de datos y operaciones que funcionan en esos datos. Las
operaciones describen los datos al resto del programa y permiten que el resto del programa cambie
los datos. La palabra "datos" en "tipo de datos abstractos" se usa libremente. Un ADT puede ser una
ventana gráfica con todas las operaciones que le afectan, un archivo y operaciones de archivo, una
tabla de tasas de seguros y las operaciones sobre ella, o cualquier otra cosa.

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!

Ejemplo de la Necesidad de un ADT


Para comenzar, aquí hay un ejemplo de un caso en el que un ADT sería útil. Llegaremos a los
detalles después de que tengamos un ejemplo del que hablar.

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 )

O podría proporcionar un nombre más específico para el atributo, algo como

currentFont.sizeInPixels = PointsToPixels( 12 )

Pero lo que no puedes hacer es tener amboscurrentFont.sizeInPixelsycurrentFont.sizeInPoints,


porque, si ambos miembros de datos están en juego,fuente actualno tendrá ninguna forma de
saber cuál de los dos debe usar. Y si cambia los tamaños en varios lugares del programa,
tendrá líneas similares repartidas por todo el programa.

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:

fuenteactual.atributo = fuenteactual.atributo o 0x02

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.atributo = fuenteactual.atributo o NEGRITA

O tal vez 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

miembros de datos directamente, lo que limita cómofuente actualpuede ser usado.

Si programa de esta manera, es probable que tenga líneas similares en muchos lugares de su
programa.

Beneficios de usar ADT


El problema no es que el enfoque ad hoc sea una mala práctica de programación. Es que
puede reemplazar el enfoque con una mejor práctica de programación que produce estos
beneficios:

Puede ocultar los detalles de implementaciónOcultar información sobre el tipo de datos de


fuente significa que si el tipo de datos cambia, puede cambiarlo en un lugar sin afectar todo el
programa. Por ejemplo, a menos que oculte los detalles de implementación en un ADT,
cambiar el tipo de datos de la primera representación en negrita a la segunda implicaría
cambiar su programa en cada lugar en el que se configuró negrita en lugar de solo en un
lugar. Ocultar la información también protege el resto del programa si decide almacenar datos
en un almacenamiento externo en lugar de en la memoria o reescribir todas las rutinas de
manipulación de fuentes en otro idioma.

V413HAV
128 Capítulo 6: Clases obreras

Los cambios no afectan a todo el programa.Si las fuentes necesitan enriquecerse y


admitir más operaciones (como cambiar a versalitas, superíndices, tachado, etc.), puede
cambiar el programa en un solo lugar. El cambio no afectará al resto del programa.

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.

Es más fácil mejorar el rendimientoSi necesita mejorar el rendimiento de la fuente, puede


recodificar algunas rutinas bien definidas en lugar de pasar por un programa completo.

El programa es más obviamente correcto.Puede reemplazar la tarea más tediosa de verificar


que declaraciones comofuenteactual.atributo = fuenteactual.atributo o 0x02son correctos con
la tarea más fácil de verificar que las llamadas aFuenteActual.SetBoldOn()son correctos Con la
primera declaración, puede tener el nombre de estructura incorrecto, el nombre de campo
incorrecto, la operación incorrecta (yen vez deo), o el valor incorrecto para el atributo (0x20en
vez de0x02). En el segundo caso, lo único que podría estar mal con la llamada a
FuenteActual.SetBoldOn()es que es una llamada al nombre de rutina incorrecto, por lo que es
más fácil ver si es correcto.

El programa se vuelve más autodocumentado.Puede mejorar afirmaciones comofuente


actual.atributo o 0x02por reemplazo0x02conAUDAZo lo que sea0x02representa, pero eso no se
compara con la legibilidad de una llamada de rutina comoFuenteActual.SetBoldOn().

3 Woodfield, Dunsmore y Shen realizaron un estudio en el que estudiantes de posgrado y de último


2
1
año de informática respondieron preguntas sobre dos programas: uno que estaba dividido en ocho

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.

Más ejemplos de ADT


Suponga que está escribiendo un software que controla el sistema de enfriamiento de un reactor nuclear.
Puede tratar el sistema de refrigeración como un tipo de datos abstracto definiendo las siguientes
operaciones para él:

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:

control de cruceroyo Licuadora Depósito de combustible

Velocidad fijada Encender tanque de llenado

Obtener la configuración actual Apagar Depósito de drenaje

Reanudar la velocidad anterior Velocidad fijada Obtener la capacidad del tanque

Desactivar Iniciar “Pulverizar instantáneamente” Obtener el estado del tanque

Detener “Pulverizar instantáneamente”

Lista Pila
Inicializar lista Luz Inicializar pila
Insertar elemento en la lista Encender Empuje el elemento a la pila

Eliminar elemento de la lista Leer el Apagar Extraiga el elemento de la pila Lea

siguiente elemento de la lista la parte superior de la pila


130 Capítulo 6: Clases obreras

Conjunto de pantallas de ayuda Menú Expediente

Agregar tema de ayuda Comenzar nuevo menú Abrir documento

Eliminar tema de ayuda Borrar menú Leer archivo

Establecer tema de ayuda actual Añadir elemento de menú escribir archivo

Mostrar pantalla de ayuda Eliminar elemento del menú Establecer ubicación de archivo actual

Eliminar pantalla de ayuda Activar elemento de menú Cerrar archivo

Mostrar índice de ayuda Desactivar elemento de menú

Retroceder a la pantalla anterior Menú de visualización Ascensor


Ocultar el menú Subir un piso Bajar
Puntero Obtener opción de menú un piso Mover a un
Obtener puntero a nueva memoria piso específico
Eliminar la memoria del Informar piso actual
puntero existente Volver al piso de origen

Cambiar la cantidad de memoria


asignada

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().

Manejo de múltiples instancias de datos con ADT en entornos no


orientados a objetos
Los lenguajes orientados a objetos brindan soporte automático para manejar múltiples
instancias de un ADT. Si ha trabajado exclusivamente en entornos orientados a objetos y
nunca ha tenido que manejar los detalles de implementación de varias instancias usted
mismo, ¡cuéntelo bien! (También puede pasar a la siguiente sección, "ADT y clases").

Si está trabajando en un entorno no orientado a objetos, como C, tendrá que crear


soporte para varias instancias de forma manual. En general, eso significa incluir
servicios para que ADT cree y elimine instancias y diseñe los otros servicios de ADT para
que puedan funcionar con varias instancias.

La fuente ADT originalmente ofrecía estos servicios:

currentFont.SetSize( sizeInPoints )
currentFont.SetBoldOn()
currentFont.SetBoldOff()
currentFont.SetItalicOn()
currentFont.SetItalicOff()
currentFont.SetTypeFace( faceName )
132 Capítulo 6: Clases obreras

En un entorno no orientado a objetos, estas funciones no se adjuntarían a una


clase y se parecerían más a esto:

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 )

La noción de unID de fuentese ha agregado como una forma de realizar un seguimiento de


varias fuentes a medida que se crean y utilizan. Para otras operaciones, puede elegir entre tres
formas de manejar la interfaz ADT:

- 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.

6.2 Interfaces de buena clase


El primer paso y probablemente el más importante para crear una clase de alta calidad es crear una
buena interfaz. Esto consiste en crear una buena abstracción para que la interfaz la represente y
asegurarse de que los detalles permanezcan ocultos detrás de la abstracción.

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,

sobre la convención (y discusiones Cuerda Dirección,


sobre múltiples estilos de Cuerda teléfono del trabajo,

codificación), consulte Cuerda teléfono de casa,

"Consideraciones sobre la Identificación del impuesto


Número de identificación tributaria,

programación en lenguaje mixto" Clasificación de Trabajo clase de trabajo

en la Sección 11.4. );
~Empleado virtual();
134 Capítulo 6: Clases obreras

// público rutinas
Nombre completo ObtenerNombre() constante;

Cuerda ObtenerDirección() constante;


Cuerda ObtenerTeléfonoTrabajo() constante;

Cuerda ObtenerTeléfonoDeCasa() constante;

Obtener número de identificación fiscal ()


Identificación del impuesto constante;
Clasificación de Trabajo ObtenerClasificaciónTrabajo() 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.

Esta evaluación de la abstracción de clases se basa en la colección de rutinas públicas de la clase, es


decir, en la interfaz de la clase. Las rutinas dentro de la clase no necesariamente presentan buenas
abstracciones individuales solo porque la clase en general lo hace, pero también deben diseñarse
para presentar buenas abstracciones. Para obtener pautas al respecto, consulte la Sección 7.2,
"Diseño en el nivel de rutina".

La búsqueda de buenas interfaces abstractas da lugar a varias pautas para crear


interfaces de clase.

Presentar un nivel consistente de abstracción en la interfaz de clase.Una buena forma de


pensar en una clase es como el mecanismo para implementar los tipos de datos abstractos
descritos en la Sección 6.1. Cada clase debe implementar uno y solo un ADT. Si encuentra una
clase que implementa más de un ADT, o si no puede determinar qué ADT implementa la clase,
es hora de reorganizar la clase en uno o más ADT bien definidos.

Aquí hay un ejemplo de una clase que presenta una interfaz que es inconsistente porque su
nivel de abstracción no es uniforme:

Ejemplo en C++ de una interfaz de clase con niveles mixtos de abstracción


clase EmployeeCensus: public ListContainer { public:

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();

La abstracción de estas rutinas Empleado Primer elemento();


está en el nivel de "lista". Empleado ÚltimoElemento();
...
privado:
...
};
136 Capítulo 6: Clases obreras

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í:

Ejemplo en C++ de una interfaz de clase con niveles de abstracción consistentes


clase Censo de empleados {
público:
...
// rutinas públicas
La abstracción de todas estas void AddEmployee (empleado empleado); void
rutinas está ahora en el nivel de RemoveEmployee (empleado empleado); Empleado
“empleado”. SiguienteEmpleado();
Empleado PrimerEmpleado();
Empleado ÚltimoEmpleado();
...
privado:
Que la clase utilice el ListContainer m_ListaEmpleados;
ListContainerLa biblioteca ahora ...
está oculta. };

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.

Asegúrese de comprender qué abstracción está implementando la claseAlgunas clases son lo


suficientemente similares por lo que debe tener cuidado de comprender qué abstracción debe capturar la
interfaz de clase. Una vez trabajé en un programa que necesitaba permitir que la información se editara en
PUNTO CLAVE
formato de tabla. Queríamos usar un control de cuadrícula simple, pero los controles de cuadrícula que
estaban disponibles no nos permitían colorear las celdas de ingreso de datos, por lo que decidimos usar un
control de hoja de cálculo que sí brindaba esa capacidad.
6.2 Interfaces de buena clase 137

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

modifica el código, consulte el Capítulo


encaja del todo con la interfaz de la clase original, pero que parece demasiado difícil de implementar
24, "Refactorización". de otra manera. por ejemplo, en elEmpleadoclass, es posible que la clase evolucione para verse así:

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;

Número de teléfono ObtenerTeléfonoTrabajo() constante;

...
bool IsJobClassificationValid( JobClassification jobClass ); bool
IsZipCodeValid( Dirección dirección );
bool IsPhoneNumberValid( PhoneNumber phoneNumber );

SqlQuery GetQueryToCreateNewEmployee() const;


SqlQuery GetQueryToModifyEmployee() const;
SqlQuery GetQueryToRetrieveEmployee() const;
...
privado:
...
};

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.

No agregue miembros públicos que sean inconsistentes con la abstracción de la interfazCada


vez que agregue una rutina a una interfaz de clase, pregunte "¿Es esta rutina consistente con la
abstracción proporcionada por la interfaz existente?" Si no, busque una forma diferente de hacer la
modificación y preservar la integridad de la abstracción.

Considere la abstracción y la cohesión juntasLas ideas de abstracción y cohesión están


estrechamente relacionadas: una interfaz de clase que presenta una buena abstracción suele tener
una fuerte cohesión. Las clases con fuerte cohesión tienden a presentar buenas abstracciones,
aunque esa relación no es tan fuerte.

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

implementación" en la Sección 5.3.


detalles de implementación. La encapsulación es el ejecutor que le impide ver los detalles incluso si
lo desea.

Los dos conceptos están relacionados porque, sin encapsulamiento, la abstracción


tiende a romperse. En mi experiencia, o tienes abstracción y encapsulación o no
tienes ninguna. No hay término medio.

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();

void SetX(float x); void


SetY( float y ); void SetZ(float
z);

está manteniendo una encapsulación perfecta. No tienes idea si la implementación


subyacente es en términos deflotarsX,y, yz, ya seaPuntoestá almacenando esos artículos
comodobles y convertirlos aflotars, o siPuntolos está almacenando en la luna y
recuperándolos de un satélite en el espacio exterior.

Evite poner detalles de implementación privados en la interfaz de una claseCon la


encapsulación verdadera, los programadores no podrían ver los detalles de implementación en
absoluto. Estarían ocultos tanto en sentido figurado como literal. En lenguajes populares, incluyendo
140 Capítulo 6: Clases obreras

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:

Ejemplo de C++ de exposición de los detalles de implementación de una clase


empleado de clase {
público:
...
Empleado(
Nombre completo nombre,
Cuerda Dirección,
Cuerda teléfono del trabajo,

Cuerda teléfono de casa,


Identificación del impuesto
Número de identificación tributaria,

Clasificación de Trabajo clase de trabajo

);
...
Nombre completo ObtenerNombre() constante;

Cuerda ObtenerDirección() constante;


...
privado:
Aquí están los detalles de Cuerda m_Nombre;

implementación expuestos. Cuerda m_Dirección;


En t m_clasetrabajo;
...
};

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.

Ejemplo de C++ de ocultar los detalles de implementación de una clase


empleado de clase {
público:
...
Empleado( ...);
...
Nombre completo ObtenerNombre() constante;

Cadena ObtenerDirección() constante;


...
privado:
Aquí los detalles de EmpleadoImplementación * m_implementación;
implementación están ocultos };
detrás del puntero.
6.2 Interfaces de buena clase 141

Ahora puede poner detalles de implementación dentro delEmpleadoImplementación


clase, que debe ser visible sólo para elEmpleadoclase y no al código que utiliza el
Empleadoclase.

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.

No haga suposiciones sobre los usuarios de la clase.Una clase debe diseñarse e


implementarse para adherirse al contrato implícito en la interfaz de clase. No debe hacer
ninguna suposición sobre cómo se usará o no esa interfaz, aparte de lo que está
documentado en la interfaz. Comentarios como el siguiente son una indicación de que una
clase está más pendiente de sus usuarios de lo que debería:

- - inicialice x, y y z a 1.0 porque DerivedClass explota


- - arriba si están inicializados a 0.0

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 dificultad de la encapsulación semántica en comparación con la encapsulación sintáctica es


similar. Sintácticamente, es relativamente fácil evitar meter la nariz en el funcionamiento interno de
otra clase simplemente declarando las rutinas y los datos internos de la claseprivado. Logrando
142 Capítulo 6: Clases obreras

la encapsulación semántica es otra cuestión completamente diferente. Estos son algunos ejemplos de las formas en que un

usuario de una clase puede romper la encapsulación semánticamente:

- No llamar a Clase AInicializarOperaciones()rutina porque sabes que la clase A


RealizarPrimeraOperación()la rutina lo llama automáticamente.

- no llamar a labase de datos.Conectar()rutina antes de llamarempleado.Recuperar (base


de datos)porque sabes que elempleado.Recuperar()La función se conectará a la base de
datos si aún no hay una conexión.

- No llamar a Clase ATerminar()rutina porque sabes que la clase A


RealizarOperaciónFinal()la rutina ya lo ha llamado.
- Usando un puntero o referencia aObjetoBcreado porObjetoAincluso despuésObjetoAha salido
del alcance, porque sabes queObjetoAmantieneObjetoBenestáticoalmacenamiento yObjetoB
seguirá siendo válido.

- Uso de Clase BMAXIMUM_ELEMENTSconstante en lugar de usar


ClaseA.MAXIMUM_ELEMENTS, porque sabes que ambos son iguales al
mismo valor.

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.

Si no puede descubrir cómo usar una clase basándose únicamente en la documentación de su


interfaz, la respuesta correcta esnopara extraer el código fuente y ver la implementación. Esa es una
buena iniciativa pero un mal juicio. La respuesta correcta es ponerse en contacto con el autor de la
clase y decirle "No sé cómo usar esta clase". La respuesta correcta por parte del autor de la clase esno
para responder a su pregunta cara a cara. La respuesta correcta para el autor de la clase es verificar
el archivo de la interfaz de clase, modificar la documentación de la interfaz de clase, volver a ingresar
el archivo y luego decir "Vea si puede entender cómo funciona ahora". Desea que este cuadro de
diálogo aparezca en el código de la interfaz para que se conserve para futuros programadores. No
desea que el diálogo ocurra únicamente en su propia mente, lo que generará dependencias
semánticas sutiles en el código del cliente que usa la clase. Y no desea que el diálogo ocurra
interpersonalmente para que solo beneficie a su código pero a nadie má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:

- Minimizar la accesibilidad de clases y miembros.

- Evitaramigoclases, porque están estrechamente acopladas.


6.3 Problemas de diseño e implementación 143

- hacer datosprivadomás bien queprotegidoen una clase base para hacer que las clases derivadas estén

menos estrechamente acopladas a la clase base.

- Evite exponer los datos de los miembros en la interfaz pública de una clase.

- Tenga cuidado con las violaciones semánticas de la encapsulación.

- Observe la “Ley de Deméter” (discutida en la Sección 6.3 de este capítulo).

El acoplamiento va de la mano con la abstracción y la encapsulación. El acoplamiento estrecho


ocurre cuando una abstracción tiene fugas o cuando se rompe la encapsulación. Si una clase ofrece
un conjunto incompleto de servicios, es posible que otras rutinas necesiten leer o escribir sus datos
internos directamente. Eso abre la clase, convirtiéndola en una caja de vidrio en lugar de una caja
negra, y elimina virtualmente la encapsulación de la clase.

6.3 Problemas de diseño e implementación


Definir buenas interfaces de clase contribuye en gran medida a crear un programa de alta calidad. El diseño
y la implementación de la clase interna también son importantes. En esta sección se analizan cuestiones
relacionadas con la contención, la herencia, las funciones y los datos de los miembros, el acoplamiento de
clases, los constructores y los objetos de valor frente a referencia.

Contención (relaciones “tiene un”)


La contención es la idea simple de que una clase contiene un elemento de datos u objeto primitivo.
Se escribe mucho más sobre la herencia que sobre la contención, pero eso se debe a que la
herencia es más complicada y propensa a errores, no porque sea mejor. La contención es la técnica
PUNTO CLAVE
del caballo de batalla en la programación orientada a objetos.

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

de identificación fiscal delEmpleadoclase.

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

144 Capítulo 6: Clases obreras

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.

Herencia (relaciones "es un")


La herencia es la idea de que una clase es una especialización de otra clase. El propósito de la
herencia es crear un código más simple definiendo una clase base que especifique elementos
comunes de dos o más clases derivadas. Los elementos comunes pueden ser interfaces de rutina,
implementaciones, miembros de datos o tipos de datos. La herencia ayuda a evitar la necesidad de
repetir código y datos en múltiples ubicaciones al centralizarlos dentro de una clase base.

Cuando decide utilizar la herencia, debe tomar varias decisiones:

- 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?

Las siguientes subsecciones explican los entresijos de tomar estas decisiones:

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.

Diseñar y documentar para herencia o prohibirlaLa herencia agrega complejidad a un


programa y, como tal, es una técnica peligrosa. Como dice el gurú de Java Joshua Bloch:
“Diseñe y documente para herencia, o prohíbalo”. Si una clase no está diseñada para ser
heredada, haga que sus miembros no seanvirtualen C++,finalen Java, o noanulable en
Microsoft Visual Basic para que no pueda heredar de él.

Adherirse al principio de sustitución de Liskov (LSP)En uno de los artículos seminales de la


programación orientada a objetos, Barbara Liskov argumentó que no se debe heredar de una
clase base a menos que la clase derivada realmente “sea” una versión más específica de la
clase base (Liskov 1988). Andy Hunt y Dave Thomas resumen LSP así: "Las subclases deben
poder utilizarse a través de la interfaz de clase base sin necesidad de que el usuario sepa la
diferencia" (Hunt y Thomas 2000).
6.3 Problemas de diseño e implementación 145

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.

Tabla 6-1 Variaciones de rutinas heredadas


reemplazable No anulable
Implementación: predeterminado Rutina anulable Rutina no reemplazable
proporcionado

Implementación: No se proporciona valor Resumen anulable No utilizado (no tiene sentido


predeterminado Rutina dejar una rutina sin definir y no
permitir que se anule)

Como sugiere la tabla, las rutinas heredadas vienen en tres sabores básicos:

- Unrutina anulable abstractasignifica que la clase derivada hereda la interfaz de la


rutina pero no su implementación.

- Unrutina anulablesignifica que la clase derivada hereda la interfaz de la rutina y una


implementación predeterminada y se le permite anular la implementación
predeterminada.

- Arutina no reemplazablesignifica que la clase derivada hereda la interfaz de la


rutina y su implementación predeterminada y no se le permite anular la
implementación de la rutina.
146 Capítulo 6: Clases obreras

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.

No "anule" una función miembro no anulableTanto C ++ como Java permiten que un


programador anule una rutina miembro no anulable, algo así. Si una función esprivadoen la
clase base, una clase derivada puede crear una función con el mismo nombre. Para el
programador que lee el código en la clase derivada, tal función puede crear confusión porque
parece que debería ser polimórfica, pero no lo es; simplemente tiene el mismo nombre. Otra
forma de establecer esta directriz es: "No reutilice nombres de rutinas de clase base no
anulables en clases derivadas".

Mover interfaces, datos y comportamientos comunes lo más alto posible en el árbol de


herenciaCuanto más alto mueva las interfaces, los datos y el comportamiento, más fácilmente
podrán usarlos las clases derivadas. ¿Qué tan alto es muy alto? Dejarabstracciónser tu guía Si
encuentra que mover una rutina más arriba rompería la abstracción del objeto superior, no lo haga.

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:

- Viola la abstracción (contrato de interfaz) presentada en elGatoclase


cambiando la semántica de su interfaz.

- Este enfoque rápidamente se sale de control cuando lo extiende a otras clases


derivadas. ¿Qué sucede cuando encuentras un gato sin cola? ¿O un gato que no caza
ratones? ¿O un gato que no bebe leche? Eventualmente terminará con clases derivadas
comoScratchlessTaillessMicelessMilklessGato.
6.3 Problemas de diseño e implementación 147

- 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.

Prefiere el polimorfismo a la verificación extensiva de tiposfrecuentemente repetidocasoLas


declaraciones a veces sugieren que la herencia podría ser una mejor opción de diseño, aunque esto
no siempre es cierto. Aquí hay un ejemplo clásico de código que pide a gritos un enfoque más
orientado a objetos:

Ejemplo en C++ de una declaración de caso que probablemente debería ser


reemplazada por polimorfismo
cambiar (forma.tipo) {
caso Shape_Circle:
forma.DrawCircle();
descanso;
caso Shape_Square:
forma.DrawSquare();
descanso;
...
}
148 Capítulo 6: Clases obreras

En este ejemplo, las llamadas aforma.DrawCircle()yforma.DrawSquare()debe ser


reemplazada por una sola rutina llamadaforma.Dibujar(), que se puede llamar
independientemente de si la forma es un círculo o un cuadrado.

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.

¿Por qué hay tantas reglas para la herencia?


Esta sección ha presentado numerosas reglas para no meterse en problemas con la herencia. El
mensaje subyacente de todas estas normas es quela herencia tiende a funcionar en contra del
imperativo técnico principal que tiene como programador, que es administrar la complejidad. En aras
PUNTO CLAVE
de controlar la complejidad, debe mantener un fuerte sesgo contra la herencia. Aquí hay un resumen
de cuándo usar la herencia y cuándo usar la contención:

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

del software: administrar la complejidad”


- Si varias clases comparten un comportamiento común pero no datos, derivarlas de
en la Sección 5.2.
una clase base común que defina las rutinas comunes.

- Si varias clases comparten datos y comportamientos comunes, heredar de una clase


base común que define las rutinas y los datos comunes.

- Hereda cuando quieras que la clase base controle tu interfaz; contiene


cuando desea controlar su interfaz.
150 Capítulo 6: Clases obreras

Funciones y datos de los miembros


Referencia cruzadaPara obtener Aquí hay algunas pautas para implementar funciones de miembros y datos de miembros de manera
más información sobre las rutinas
efectiva.
en general, consulte el Capítulo 7,

"Rutinas de alta calidad".


Mantenga el número de rutinas en una clase lo más pequeño posibleUn estudio de programas
C++ encontró que un mayor número de rutinas por clase se asociaba con mayores tasas de errores
(Basili, Briand y Melo 1996). Sin embargo, se encontró que otros factores en competencia eran más
significativos, incluidos los árboles de herencia profundos, una gran cantidad de rutinas llamadas
dentro de una clase y un fuerte acoplamiento entre clases. Evalúe la compensación entre minimizar
el número de rutinas y estos otros factores.

No permita funciones miembro generadas implícitamente y operadores que no desee A


veces encontrará que desea deshabilitar ciertas funciones, tal vez desee deshabilitar la
asignación, o no desea permitir que se construya un objeto. Podría pensar que, dado que el
compilador genera operadores automáticamente, está atascado permitiendo el acceso. Pero
en tales casos, puede rechazar esos usos declarando el constructor, el operador de asignación
u otra función u operadorprivado, lo que impedirá que los clientes accedan a él. (Hacer que el
constructor sea privado es una técnica estándar para definir una clase singleton, que se
analiza más adelante en este capítulo).

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:

- Número de tipos de objetos instanciados

- Número de llamadas de rutina directas diferentes en objetos instanciados

- Número de llamadas de rutina en objetos devueltos por otros objetos instanciados


6.3 Problemas de diseño e implementación 151

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:

Ejemplo de Java de hacer cumplir un Singleton con un constructor privado


MaxId de clase pública {
// constructores y destructores private
Aquí está el MaxId() {
constructor privado. ...
}
...

// 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(); . . .

El constructor privado se llama sólo cuando elestáticoobjetom_instanciase inicializa. En


este enfoque, si desea hacer referencia a laID máx.singleton, simplemente se referiría a
MaxId.GetInstance().

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).

6.4 Razones para crear una clase


Referencia cruzadaLas Si cree todo lo que lee, es posible que tenga la idea de que la única razón para crear una clase es
razones para crear clases y
modelar objetos del mundo real. En la práctica, las clases se crean por muchas más razones que esa.
rutinas se superponen. Ver
Sección 7.1. Aquí hay una lista de buenas razones para crear una clase.

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

mundo real" en la Sección 5.3.


mundo real que modele su programa. Coloque los datos necesarios para el objeto en la clase y luego
cree rutinas de servicio que modelen el comportamiento del objeto. Vea la discusión de los ADT en la
Sección 6.1 para ver ejemplos.

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.

aislar la complejidadLa complejidad en todas sus formas (algoritmos complicados, grandes


conjuntos de datos, protocolos de comunicación intrincados, etc.) es propensa a errores. Si ocurre un
error, será más fácil encontrarlo si no se propaga a través del código sino que se localiza dentro de
una clase. Los cambios que surjan de la corrección del error no afectarán a otro código porque solo
se tendrá que corregir una clase; el otro código no se modificará. Si encuentra un algoritmo mejor,
más simple o más confiable, será más fácil reemplazar el algoritmo anterior si se ha aislado en una
clase. Durante el desarrollo, será más fácil probar varios diseños y quedarse con el que mejor
funcione.

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,

consulte la Sección 13.3, “Datos


varios beneficios en comparación con trabajar con datos globales directamente. Puede cambiar la
globales”. estructura de los datos sin cambiar su programa. Puede monitorear los accesos a los datos. La
disciplina de usar rutinas de acceso también lo alienta a pensar si los datos son realmente globales; a
menudo se hace evidente que los "datos globales" son realmente solo datos de objetos.

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

información)" en la Sección 5.3.


El control de dispositivos (archivos, conexiones a bases de datos, impresoras, etc.) es otro. Usar una
clase para leer y escribir en una base de datos es una forma de control centralizado. Si es necesario
convertir la base de datos en un archivo sin formato o en datos en memoria, los cambios afectarán
solo a una clase.
154 Capítulo 6: Clases obreras

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

requerida, consulte "Un programa


realizan el trabajo necesario para hacer que las clases sean reutilizables como un proyecto especial al
contiene código que parece que podría final del proyecto principal o como el primer paso en un nuevo proyecto. Este enfoque ayuda a evitar
ser necesario algún día" en la Sección
la creación de funcionalidades que no son necesarias y que agregan complejidad innecesariamente.
24.2.

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.

Realizar una refactorización específicaMuchas de las refactorizaciones específicas descritas en el


Capítulo 24, "Refactorización", dan como resultado nuevas clases, incluida la conversión de una clase en
dos, la ocultación de un delegado, la eliminación de un intermediario y la introducción de una clase de
extensión. Estas nuevas clases podrían estar motivadas por el deseo de lograr mejor cualquiera de los
objetivos descritos a lo largo de esta sección.

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,

consulte la Sección 13.1, “Estructuras”.


conviertan en atributos de una o más clases.

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.

Resumen de razones para crear una clase


Aquí hay una lista resumida de las razones válidas para crear una clase:

- Modelar objetos del mundo real

- Modelar objetos abstractos

- Reducir la complejidad

- aislar la complejidad

- Ocultar detalles de implementación

- Limitar los efectos de los cambios

- Ocultar datos globales


156 Capítulo 6: Clases obreras

- Optimizar el paso de parámetros

- Hacer puntos centrales de control

- Facilitar el código reutilizable

- Plan para una familia de programas

- Operaciones relacionadas con paquetes

- Realizar una refactorización específica

6.5 Problemas específicos del idioma


Los enfoques de las clases en diferentes lenguajes de programación varían de manera interesante.
Considere cómo anula una rutina miembro para lograr polimorfismo en una clase derivada. En Java,
todas las rutinas se pueden anular de forma predeterminada y se debe declarar una rutina finalpara
evitar que una clase derivada la invalide. En C++, las rutinas no se pueden anular de forma
predeterminada. Una rutina debe ser declaradavirtualen la clase base para ser reemplazable. En
Visual Basic, una rutina debe ser declaradaanulableen la clase base y la clase derivada debe usar el
anulapalabra clave.

Estas son algunas de las áreas relacionadas con la clase que varían significativamente según el
idioma:

- Comportamiento de constructores y destructores anulados en un árbol de herencia

- Comportamiento de constructores y destructores bajo condiciones de manejo de excepciones

- Importancia de los constructores predeterminados (constructores sin argumentos)

- Momento en el que se llama a un destructor o finalizador

- Sabiduría de anular los operadores integrados del lenguaje, incluida la asignación y la


igualdad

- Cómo se maneja la memoria a medida que se crean y destruyen objetos o cuando se


declaran y quedan fuera del alcance

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.

6.6 Más allá de las clases: paquetes


Referencia cruzadaPara obtener Las clases son actualmente la mejor manera para que los programadores logren la modularidad. Pero la
más información sobre la distinción
modularidad es un gran tema y se extiende más allá de las clases. Durante las últimas décadas, el desarrollo
entre clases y paquetes, consulte

"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.

Es evidente que podríamos apoyar mejor los objetivos de abstracción y encapsulación si


tuviéramos buenas herramientas para agregar grupos de objetos. Ada apoyó la noción de
paquetes hace más de una década y Java admite paquetes en la actualidad. Si está
programando en un lenguaje que no admite paquetes directamente, puede crear su propia
versión de un paquete para programadores deficientes y aplicarla a través de estándares de
programación que incluyen lo siguiente:

- Convenciones de nomenclatura que diferencian qué clases son públicas y cuáles son
para uso privado del paquete

- Convenciones de nomenclatura, convenciones de organización de código (estructura del


proyecto) o ambas que identifican a qué paquete pertenece cada clase

- 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”.

cc2e.com/0672 LISTA DE VERIFICACIÓN: Calidad de la clase

Referencia cruzadaEsta es una lista de Tipos de datos abstractos


verificación de consideraciones sobre la
- ¿Ha pensado en las clases de su programa como tipos de datos abstractos y ha
calidad de la clase. Para obtener una lista
evaluado sus interfaces desde ese punto de vista?
de los pasos utilizados para crear una

clase, consulte la lista de verificación "El

proceso de programación de
Abstracción
pseudocódigo" en el Capítulo 9, página - ¿La clase tiene un propósito central?
233.

- ¿La clase está bien nombrada y su nombre describe su propósito central?

- ¿La interfaz de la clase presenta una abstracción consistente?

- ¿La interfaz de la clase deja en claro cómo debe usar la clase?


- ¿Es la interfaz de la clase lo suficientemente abstracta como para no tener que pensar
en cómo se implementan sus servicios? ¿Puedes tratar la clase como una caja negra?

- ¿Los servicios de la clase son lo suficientemente completos como para que otras clases no tengan que

entrometerse con sus datos internos?

- ¿Se ha sacado de la clase información no relacionada?

- ¿Ha pensado en subdividir la clase en clases componentes y la ha


subdividido tanto como ha podido?
- ¿Está preservando la integridad de la interfaz de la clase a medida que modifica la
clase?
158 Capítulo 6: Clases obreras

Encapsulación
- ¿La clase minimiza la accesibilidad a sus miembros?

- ¿La clase evita exponer los datos de los 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?

- ¿Es la clase independiente de otras clases? ¿Está débilmente acoplado?

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?

- ¿La documentación de la clase describe la estrategia de herencia?

- ¿Las clases derivadas evitan las rutinas no reemplazables?


- ¿Están las interfaces, los datos y el comportamiento comunes lo más alto posible en el
árbol de herencia?

- ¿Los árboles de herencia son bastante superficiales?

- ¿Todos los miembros de datos de la clase base son privados en lugar de protegidos?

Otros problemas de implementación


- ¿La clase contiene alrededor de siete miembros de datos o menos?

- ¿La clase minimiza las llamadas de rutina directas e indirectas a otras clases?

- ¿La clase colabora con otras clases solo en la medida absolutamente


necesaria?

- ¿Todos los datos de los miembros están inicializados en el constructor?

- ¿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?

Problemas específicos del idioma

- ¿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.

Cornell, Gary y Jonathan Morrison.Programación VB .NET: una guía para programadores


experimentados. Berkeley, CA: Apress, 2002.

Barwell, Fred, et al.VB.NET profesional, 2ª ed. Wrox, 2002.


160 Capítulo 6: Clases obreras

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

Rutinas de alta calidad


cc2e.com/0778 Contenido

- 7.1 Razones válidas para crear una rutina: página 164

- 7.2 Diseño a nivel de rutina: página 168

- 7.3 Buenos nombres de rutinas: página 171

- 7.4 ¿Cuánto puede durar una rutina?: página 173

- 7.5 Cómo usar los parámetros de rutina: página 174

- 7.6 Consideraciones especiales en el uso de funciones: página 181

- 7.7 Rutinas macro y rutinas en línea: página 182

Temas relacionados

- Pasos en la construcción de rutinas: Sección 9.3

- Clases obreras: Capítulo 6

- Técnicas generales de diseño: Capítulo 5

- Arquitectura de software: Sección 3.5

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

Ejemplo en C++ de una rutina de baja calidad


void HandleStuff(CORP_DATA & inputRec, int crntQtr, EMP_DATA empRec,
double & estimRevenue, double ytdRevenue, int screenX, int screenY, COLOR_TYPE &
CODIFICACIÓN newColor, COLOR_TYPE & prevColor, StatusType & status, int gastType )
HORROR

{
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]; }

más si ( tipo de gasto == 3 )


beneficio[i] = ingresos[i] - gastos.tipo3[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 tiene mala fama.ManejarCosas()no le dice nada acerca de lo que hace la


rutina.

- La rutina no está documentada. (El tema de la documentación se extiende más allá de


los límites de las rutinas individuales y se analiza en el Capítulo 32, “Código de
autodocumentación”).

- La rutina tiene un mal diseño. La organización física del código en la página da


algunas pistas sobre su organización lógica. Las estrategias de diseño se utilizan al
azar, con diferentes estilos en diferentes partes de la rutina. Compara los estilos
dondetipo de gasto == 2ytipo de gasto == 3. (El diseño se trata en el Capítulo 31,
“Diseño y estilo”).

- La variable de entrada de la rutina,entradaRec,está cambiado. Si es una variable de


entrada, su valor no debe modificarse (y en C++ debe declararseconstante). Si se
supone que el valor de la variable debe modificarse, la variable no debe llamarse
entradaRec.

- La rutina lee y escribe variables globales: lee degastocorpy escribe alucro.


Debería comunicarse con otras rutinas más directamente que leyendo y
escribiendo variables globales.
Una rutina de baja calidad 163

- 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 no se defiende contra datos incorrectos. SiCrntQtres igual0, la expresion


ytdRevenue * 4.0 / (doble) crntQtrprovoca un error de división por cero.

- 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”.

- Algunos de los parámetros de la rutina no se utilizan:pantallaXypantallaYno están


referenciados dentro de la rutina.

- Uno de los parámetros de la rutina se pasa incorrectamente:anteriorColorestá etiquetado


como un parámetro de referencia (&) aunque no se le asigne un valor dentro de la rutina.

- La rutina tiene demasiados parámetros. El límite superior para un número comprensible


de parámetros es de unos 7; esta rutina tiene 11. Los parámetros están dispuestos de
una manera tan ilegible que la mayoría de la gente no intentaría examinarlos de cerca o
incluso contarlos.

- 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

también es un buen candidato


cualquier lenguaje de programación, y es un delito abusar de este estadista de la informática
para el mayor invento en con un código como el del ejemplo que acabamos de mostrar.
informática. Para obtener detalles

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

7.1 Razones válidas para crear una rutina


Aquí hay una lista de razones válidas para crear una rutina. Los motivos se superponen un
poco y no pretenden formar un conjunto ortogonal.

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

si (nodo <> NULL) entonces


while ( nodo.siguiente <> NULL ) hacer
nodo = nodo.siguiente
hojaNombre = nodo.nombre
terminar mientras

más
nombre de la hoja = ""
terminara si

se puede leer una declaración como esta:

nombreHoja = ObtenerNombreHoja( nodo )

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.

Ocultar operaciones de punteroLas operaciones de puntero tienden a ser difíciles de leer y


propensas a errores. Al aislarlos en rutinas, puede concentrarse en la intención de la operación en
lugar de en la mecánica de la manipulación del puntero. Además, si las operaciones se realizan en un
solo lugar, puede estar más seguro de que el código es correcto. Si encuentra un tipo de datos mejor
que los punteros, puede cambiar el programa sin traumatizar el código que habría utilizado los
punteros.

Mejora la portabilidadEl uso de rutinas aísla capacidades no portátiles, identificando y aislando


explícitamente el trabajo de portabilidad futuro. Las capacidades no portátiles incluyen funciones de
lenguaje no estándar, dependencias de hardware, dependencias del sistema operativo, etc.

Simplifique pruebas booleanas complicadasRara vez es necesario comprender pruebas


booleanas complicadas en detalle para comprender el flujo del programa. Poner una prueba de
este tipo en una función hace que el código sea más legible porque (1) los detalles de la prueba
están fuera del camino y (2) un nombre de función descriptivo resume el propósito de la prueba.

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.

Mejorar el rendimientoPuede optimizar el código en un lugar en lugar de en varios lugares. Tener


el código en un solo lugar hará que sea más fácil perfilar para encontrar ineficiencias. Centralizar el
código en una rutina significa que una sola optimización beneficia a todo el código que usa esa
rutina, ya sea que la use directa o indirectamente. Tener el código en un solo lugar hace que sea
práctico recodificar la rutina con un algoritmo más eficiente o en un lenguaje más rápido y eficiente.
166 Capítulo 7: Rutinas de alta calidad

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

información)" en la Sección 5.3.


grande. (La mejor duración para una rutina se analiza en la Sección 7.4, “¿Cuánto tiempo puede durar una
rutina?”)

Operaciones que parecen demasiado simples para ponerlas en rutinas

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:

Ejemplo de pseudocódigo de un cálculo


puntos = unidades de dispositivo * (POINTS_PER_INCH / DeviceUnitsPerInch() )

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:

Ejemplo de pseudocódigo de un cálculo convertido en una función


Función DeviceUnitsToPoints ( deviceUnits Integer ): Integer
UnidadesDeDispositivoAPuntos = UnidadesDeDispositivo *

(POINTS_PER_INCH / DeviceUnitsPerInch() ) Función final

Cuando se sustituyó la rutina por el código en línea, la docena de líneas de código se


parecían más o menos a esta:

Ejemplo de pseudocódigo de una llamada de función a una función de cálculo


puntos = DeviceUnitsToPoints( deviceUnits )

Esta línea es más legible, incluso se acerca a la autodocumentación.

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

Ejemplo de pseudocódigo de un cálculo que se expande bajo mantenimiento


Función DeviceUnitsToPoints( deviceUnits: Integer ) Integer;
si (Unidades de dispositivo por pulgada () <> 0)
UnidadesDeDispositivoAPuntos = UnidadesDeDispositivo *
( PUNTOS_POR_PULGADA / UnidadesDeDispositivoPorPulgada() )
más
DeviceUnitsToPoints = 0 finaliza
si
función final

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.

Resumen de razones para crear una rutina


Aquí hay una lista resumida de las razones válidas para crear una rutina:

- Reducir la complejidad

- Introducir una abstracción intermedia comprensible

- Evite el código duplicado

- Subclases de soporte

- Ocultar secuencias

- Ocultar operaciones de puntero

- Mejora la portabilidad

- Simplifique pruebas booleanas complicadas

- 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

- Ocultar detalles de implementación

- Limitar los efectos de los cambios

- Ocultar datos globales

- Hacer puntos centrales de control

- Facilitar el código reutilizable

- Realizar una refactorización específica


168 Capítulo 7: Rutinas de alta calidad

7.2 Diseño a nivel de rutina


La idea de cohesión fue presentada en un artículo de Wayne Stevens, Glenford Myers y Larry
Constantine (1974). Otros conceptos más modernos, incluidos la abstracción y la encapsulación,
tienden a brindar más información a nivel de clase (y, de hecho, han superado en gran medida la
cohesión a nivel de clase), pero la cohesión todavía está viva y bien como heurística de diseño de
caballo de batalla a nivel individual. -nivel de rutina.

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

cohesión fuerte” en la Sección 5.3.


relacionadas están las operaciones en una rutina? Una función comoCoseno()es perfectamente
cohesivo porque toda la rutina está dedicada a realizar una función. Una función comoCosenoYTan()
tiene menor cohesión porque trata de hacer más de una cosa. El objetivo es que cada rutina haga
una cosa bien y no haga nada más.

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).

Las discusiones sobre la cohesión generalmente se refieren a varios niveles de cohesión.


Comprender los conceptos es más importante que recordar términos específicos. Use los conceptos
como ayuda para pensar en cómo hacer que las rutinas sean lo más cohesivas posible.

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.

Varios otros tipos de cohesión normalmente se consideran menos que ideales:

- 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.

- Cohesión temporalOcurre cuando las operaciones se combinan en una rutina porque


todas se realizan al mismo tiempo. Ejemplos típicos seríanPuesta en marcha(),
CompletarNuevoEmpleado(), yCerrar(). Algunos programadores consideran que la
cohesión temporal es inaceptable porque a veces se asocia con malas prácticas de
programación, como tener una mezcolanza de código en unPuesta en marcha()rutina.

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.

Este ejemplo plantea la cuestión de elegir un nombre que describa la rutina en el


nivel adecuado de abstracción. Podrías decidir nombrar la rutinaReadConfig-
FileInitScratchFileEtc(), lo que implicaría que la rutina sólo tenía una cohesión
coincidente. si lo nombrasPuesta en marcha(), sin embargo, sería claro que tenía
un solo propósito y claro que tenía cohesión funcional.

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

- Cohesión procesalOcurre cuando las operaciones en una rutina se realizan en un orden


específico. Un ejemplo es una rutina que obtiene el nombre de un empleado, luego una
dirección y luego un número de teléfono. El orden de estas operaciones es importante solo
porque coincide con el orden en que se solicitan al usuario los datos en la pantalla de entrada.
Otra rutina obtiene el resto de los datos de los empleados. La rutina tiene cohesión procesal
porque pone un conjunto de operaciones en un orden específico y las operaciones no
necesitan combinarse por ningún otro motivo.

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.

- Cohesión lógicaocurre cuando varias operaciones se introducen en la misma rutina y una de


las operaciones se selecciona mediante un indicador de control que se pasa. Se llama
cohesión lógica porque el flujo de control o "lógica" de la rutina es lo único que une las
operaciones: todos están en un gransideclaración ocaso declaración juntos. No es porque las
operaciones estén relacionadas lógicamente en ningún otro sentido. Teniendo en cuenta que
el atributo definitorio de la cohesión lógica es que las operaciones no están relacionadas, un
mejor nombre podría ser "cohesión ilógica".

Un ejemplo sería unEntradaTodo()rutina que ingresa los nombres de los clientes, la


información de la tarjeta de tiempo de los empleados o los datos de inventario según un
indicador que se pasa a la rutina. Otros ejemplos seríanCalcularTodo(),EditarTodo(),Imprimir
todo(), y Salvar a todos(). El principal problema con tales rutinas es que no debería necesitar
pasar una bandera para controlar el procesamiento de otra rutina. En lugar de tener una
rutina que realice una de tres operaciones distintas, dependiendo de un indicador que se le
pase, es más limpio tener tres rutinas, cada una de las cuales realiza una operación distinta. Si
las operaciones usan parte del mismo código o comparten datos, el código debe moverse a
una rutina de nivel inferior y las rutinas deben empaquetarse en una clase.

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

de alto nivel es si el sistema debe


caso, si la única función de la rutina es despachar comandos y no realiza ningún
usar un casoenunciado en lugar procesamiento en sí, generalmente es un buen diseño. El término técnico para este tipo
de polimorfismo. Para obtener
de rutina es "controlador de eventos". Un controlador de eventos se usa a menudo en
más información sobre este tema,

consulte "Reemplazar
entornos interactivos como Apple Macintosh, Microsoft Windows y otros entornos de
condicionales con polimorfismo GUI.
(especialmentecaso

declaraciones)” en la Sección 24.3


- Cohesión coincidenteOcurre cuando las operaciones en una rutina no tienen una relación
perceptible entre sí. Otros buenos nombres son "sin cohesión" o "cohesión caótica". La rutina
C++ de baja calidad al comienzo de este capítulo tenía una cohesión coincidente. Es difícil
convertir la cohesión coincidente en un tipo mejor de cohesión; por lo general, es necesario
realizar un rediseño y una reimplementación más profundos.
7.3 Buenos nombres de rutinas 171

Ninguno de estos términos es mágico o sagrado. Aprende las ideas en lugar de la


terminología. Casi siempre es posible escribir rutinas con cohesión funcional, así que centre
su atención en la cohesión funcional para obtener el máximo beneficio.
PUNTO CLAVE

7.3 Buenos nombres de rutinas


Referencia cruzadaPara obtener detalles Un buen nombre para una rutina describe claramente todo lo que hace la rutina. Aquí hay
sobre cómo nombrar variables, consulte
pautas para crear nombres de rutina efectivos:
el Capítulo 11, "El poder de los nombres

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

7.6, “Consideraciones especiales


impresora.EstáListo(), ybolígrafo.ColorActual()son todos buenos nombres de funciones que indican con
en el uso de funciones”, más precisión lo que devuelven las funciones.
adelante en este capítulo.

Para nombrar un procedimiento, use un verbo fuerte seguido de un objetoUn procedimiento


con cohesión funcional suele realizar una operación sobre un objeto. El nombre debe reflejar lo que
hace el procedimiento, y una operación en un objeto implica un nombre de verbo más objeto.
Imprimir documento(),CalcIngresosMensuales(),ComprobarOrdeninfo(), yRepaginarDocumento()
son ejemplos de buenos nombres de procedimientos.

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:

agregar eliminar incrementar/decrementar abierto cerrado

empezar/finalizar insertar/eliminar mostrar ocultar

crear/destruir bloqueo y desbloqueo origen Destino


primero último mínimo máximo iniciar/detener

obtener/poner siguiente anterior arriba abajo

obtener/establecer viejo nuevo

Establecer convenciones para operaciones comunesEn algunos sistemas, es importante


distinguir entre diferentes tipos de operaciones. Una convención de nomenclatura suele ser la
forma más fácil y fiable de indicar estas distinciones.

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()

losEmpleadola clase expuso suidentificaciónobjeto, que a su vez expuso suObtener()rutina. los


Dependienteclase expuso unObtenerId()rutina. losSupervisorla clase hizo elidentificaciónsu valor de
retorno predeterminado. losCandidatoclase hizo uso del hecho de que elidentificaciónel valor de
retorno predeterminado del objeto era elidentificación, y expuso laidentificaciónobjeto. A la mitad del
proyecto, nadie podía recordar cuál de estas rutinas se suponía que debía usarse en qué objeto, pero
en ese momento se había escrito demasiado código para volver atrás y hacer que todo fuera
coherente. En consecuencia, cada persona del equipo tuvo que dedicar una cantidad innecesaria de
materia gris a recordar el detalle intrascendente de qué sintaxis se usó en qué clase para recuperar el
identificación. Una convención de nomenclatura para recuperaridentificacións habría eliminado esta
molestia.

7.4 ¿Cuánto puede durar una rutina?


En su camino a América, los Peregrinos discutieron sobre la mejor duración máxima para una
rutina. Después de discutirlo durante todo el viaje, llegaron a Plymouth Rock y comenzaron a
redactar el Mayflower Compact. Todavía no habían resuelto la cuestión de la longitud máxima,
y como no podían desembarcar hasta que firmaron el pacto, se dieron por vencidos y no lo
incluyeron. El resultado ha sido un debate interminable desde entonces sobre cuánto puede
durar una rutina.

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

174 Capítulo 7: Rutinas de alta calidad

- 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.

7.5 Cómo utilizar los parámetros de rutina


3
2
Las interfaces entre rutinas son algunas de las áreas más propensas a errores de un programa. Un
1
estudio citado con frecuencia por Basili y Perricone (1984) encontró que el 39 por ciento de todos los

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

comentarios” en la Sección 32.5. Para


segundo lugar y solo de salida en tercer lugar. Este orden implica la secuencia de operaciones que ocurren
obtener detalles sobre los parámetros dentro de la rutina: ingresar datos, cambiarlos y devolver un resultado. Estos son ejemplos de listas de
de formato, consulte la Sección 31.7,
parámetros en Ada:
“Diseño de rutinas”.
7.5 Cómo utilizar los parámetros de rutina 175

Ejemplo de Ada de parámetros en orden de entrada-modificación-salida


procedimiento InvertirMatriz(
usos de adaenyafuerapalabras Matriz original: en Matriz;
clave para aclarar los parámetros matriz de resultados: fuera Matriz
de entrada y salida. );
...

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.

Considere la posibilidad de crear su propiaenyafuerapalabras claveOtros lenguajes modernos no


soportan elenyafuerapalabras clave como lo hace Ada. En esos idiomas, es posible que aún pueda
usar el preprocesador para crear su propioenyafuerapalabras clave:

Ejemplo en C++ de cómo definir el suyo propioEnyAfueraPalabras clave


# definir EN
# definir AFUERA

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

Ejemplo de Java de uso inadecuado de parámetros de entrada


int Muestra( int inputVal ) {
inputVal = inputVal * CurrentMultiplier( inputVal ); inputVal = inputVal +
CurrentAdder( inputVal );
...
En este punto,valor de devolver valor de entrada;

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.

¿Cómo resuelves el problema? ¿Puedes resolverlo cambiando el nombre?valor de entrada?


Probablemente no. Podrías nombrarlo algo comovalor de trabajo, pero esa es una solución
incompleta porque el nombre no indica que el valor original de la variable proviene de fuera de la
rutina. Podrías nombrarlo algo ridículo comovalor de entrada que se convierte en valor de trabajoo
renunciar por completo y nombrarloXovalor, pero todos estos enfoques son débiles.

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:

Ejemplo Java de buen uso de parámetros de entrada


int Muestra( int inputVal ) {
int valor de trabajo = valor de entrada;
valor de trabajo = valor de trabajo * MultiplicadorActual (valor de trabajo); valor
de trabajo = valor de trabajo + sumador actual (valor de trabajo);
...
Si necesita usar el valor
original devalor de entrada ...
aquí o en otro lugar, todavía volver valor de trabajo;
está disponible. }

Introduciendo la nueva variablevalor de trabajoaclara el papel devalor de entraday elimina la posibilidad de


utilizar erróneamentevalor de entradaEn el momento equivocado. (No tome este razonamiento como una
justificación para nombrar literalmente una variablevalor de entradaovalor de trabajo. En general, valor de
entradayvalor de trabajoson nombres terribles para las variables, y estos nombres se usan en este ejemplo
solo para aclarar los roles de las variables).

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

Capítulo 8, "Programación defensiva".


las hace. No es una pérdida de esfuerzo documentar sus suposiciones tanto en la rutina misma
Para obtener detalles sobre la como en el lugar donde se llama la rutina. No espere hasta que haya escrito la rutina para
documentación, consulte el Capítulo
regresar y escribir los comentarios; no recordará todas sus suposiciones. Incluso mejor que
32, “Autodocumentación

Código."
comentar sus suposiciones, use aserciones para ponerlas en código.

¿Qué tipo de suposiciones de interfaz sobre parámetros debe documentar?

- Si los parámetros son solo de entrada, modificados o solo de salida

- Unidades de parámetros numéricos (pulgadas, pies, metros, etc.)

- Significados de los códigos de estado y valores de error si no se usan los tipos enumerados

- Rangos de valores esperados

- Valores específicos que nunca deberían aparecer

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

abstracción" en la Sección 6.2.


está pasando los mismos datos a muchas rutinas diferentes, agrupe las rutinas en una clase y trate
los datos de uso frecuente como datos de clase.

Considere una convención de nomenclatura de entrada, modificación y salida para parámetros


Si encuentra que es importante distinguir entre los parámetros de entrada, modificación y salida,
establezca una convención de nomenclatura que los identifique. Podrías prefijarlos coni_, metro_, y
o_. Si te sientes detallado, puedes prefijarlos conAporte_,Modificar_, y Producción_.
7.5 Cómo utilizar los parámetros de rutina 179

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).

Si se encuentra cambiando con frecuencia la lista de parámetros a la rutina, con los


parámetros provenientes del mismo objeto cada vez, eso es una indicación de que
debe pasar el objeto completo en lugar de elementos específicos.
180 Capítulo 7: Rutinas de alta calidad

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:

Ejemplo de Visual Basic de identificación explícita de parámetros


Función Privada Distancia3d( _
Aquí es donde se declaran ByVal xDistancia como coordenada, _ ByVal
los parámetros formales. yDistancia como coordenada, _ ByVal
zDistancia como coordenada _
)
...
función final
...
Velocidad de función privada (_
ByVal latitud como Coordenada, _ ByVal
longitud como Coordenada, _ ByVal
elevación como Coordenada _
)
...
Aquí es donde los Distancia = Distancia3d( xDistancia := latitud, yDistancia := longitud, _
parámetros reales se asignan zDistancia := elevación )
a los parámetros formales. ...
función final

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

7.6 Consideraciones especiales en el uso de funciones


Los lenguajes modernos como C++, Java y Visual Basic admiten funciones y procedimientos. Una
función es una rutina que devuelve un valor; un procedimiento es una rutina que no lo hace. En C++,
todas las rutinas se denominan típicamente "funciones"; sin embargo, una función con unvacíoel tipo
de retorno es semánticamente un procedimiento. La distinción entre funciones y procedimientos es
tanto una distinción semántica como sintáctica, y la semántica debe ser su guía.

Cuándo usar una función y cuándo usar un procedimiento


Los puristas argumentan que una función debe devolver solo un valor, tal como lo hace una función
matemática. Esto significa que una función solo tomaría parámetros de entrada y devolvería su único valor a
través de la propia función. La función siempre sería nombrada por el valor que devolvió, comopecado(),
Identificación del cliente(), yAltura de la pantalla()son. Un procedimiento, por otro lado, podría tomar
parámetros de entrada, modificación y salida, tantos de cada uno como quisiera.

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:

if (reporte.FormatOutput(reporte formateado) = Correcto) entonces...

En este ejemplo,informe.FormatOutput()opera como un procedimiento en el sentido de que tiene un


parámetro de salida,informe formateado, pero técnicamente es una función porque la rutina misma
devuelve un valor. ¿Es esta una forma válida de usar una función? En defensa de este enfoque,
podría sostener que el valor de retorno de la función no tiene nada que ver con el propósito principal
de la rutina, formatear la salida o con el nombre de la rutina,informe.FormatOutput().En ese sentido,
opera más como un procedimiento, incluso si es técnicamente una función. El uso del valor devuelto
para indicar el éxito o el fracaso del procedimiento no es confuso si la técnica se usa de manera
consistente.

La alternativa es crear un procedimiento que tenga una variable de estado como parámetro explícito,
que promueva código como este fragmento:

informe.FormatOutput(informe formateado, estado de salida) si (estado


de salida = Éxito) entonces...

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:

estado de salida = informe.FormatOutput(reporte formateado) si (estado


de salida = éxito) entonces...
182 Capítulo 7: Rutinas de alta calidad

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

Establecer el valor de retorno de la función

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.

7.7 Rutinas macro y rutinas en línea


Referencia cruzadaIncluso si su Las rutinas creadas con macros de preprocesador requieren algunas consideraciones únicas.
idioma no tiene un preprocesador de
Las siguientes reglas y ejemplos se refieren al uso del preprocesador en C++. Si está utilizando
macros, puede crear el suyo propio.

Para obtener más información,


un lenguaje o preprocesador diferente, adapte las reglas a su situación.
consulte la Sección 30.5, “Creación de

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:

Ejemplo en C++ de una macro que no se expande correctamente


# define Cubo( a ) a*a*a

Si pasa esta macro valores no atómicos paraa, no hará la multiplicación


correctamente. Si usas la expresiónCubo( x+1 ), se expande ax+1 * x + 1 * x + 1, que,
debido a la precedencia de los operadores de multiplicación y suma, no es lo que
desea. Una versión mejor, pero aún no perfecta, de la macro se ve así:

Ejemplo en C++ de una macro que aún no se expande correctamente


# define Cubo( a ) (a)*(a)*(a)
7.7 Rutinas macro y rutinas en línea 183

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:

Ejemplo en C++ de una macro que funciona


# define Cubo( a ) ((a)*(a)*(a))

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:

Ejemplo en C++ de una macro que no funciona con varias declaraciones


# define LookupEntry( clave, índice ) \ índice =
(clave - 10) / 5; \
CODIFICACIÓN índice = min( índice, MAX_INDEX ); \ índice =
HORROR
max( índice, MIN_INDEX );
...

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:

índice = (clave - 10) / 5;

Para evitar este problema, rodee la macro con llaves:

Ejemplo en C++ de una macro con varias instrucciones que funciona


# define LookupEntry (clave, índice) { \ índice =
(clave - 10) / 5; \
índice = min( índice, MAX_INDEX ); \ índice =
max( índice, MIN_INDEX ); \
}

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

Seguir esta recomendación conlleva cierto riesgo. Si usas comúnmente++y--como efectos


secundarios (como parte de otras declaraciones), se quemará cuando use macros que crea que son
rutinas. Teniendo en cuenta los otros problemas con los efectos secundarios, esta es otra razón más
para evitar el uso de efectos secundarios.

Limitaciones en el uso de macrorutinas


Los lenguajes modernos como C++ ofrecen numerosas alternativas al uso de macros:

- constantepara declarar valores constantes

- en líneapara definir funciones que se compilarán como código en línea

- modelopara definir operaciones estándar comomin,máximo, y así sucesivamente de forma segura

- enumeraciónpara definir tipos enumerados

- definición de tipopara definir sustituciones de tipos simples

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.

cc2e.com/0792 LISTA DE VERIFICACIÓN: rutinas de alta calidad

Referencia cruzadaEsta es una lista de Problemas generales


verificación de consideraciones sobre la
- ¿Es suficiente la razón para crear la rutina?
calidad de la rutina. Para obtener una lista

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?

- ¿El nombre de la rutina describe todo lo que hace la rutina?

- ¿Ha establecido convenciones de nomenclatura para operaciones comunes?

- ¿Tiene la rutina una cohesión funcional fuerte, hacer una y sólo


una cosa y hacerlo bien?
- ¿Las rutinas tienen un acoplamiento débil? ¿Son las conexiones de la rutina
con otras rutinas pequeñas, íntimas, visibles y flexibles?

- ¿La duración de la rutina está determinada naturalmente por su función y lógica, en


lugar de por un estándar de codificación artificial?

Problemas de paso de parámetros

- ¿La lista de parámetros de la rutina, tomada como un todo, presenta una abstracción de
interfaz consistente?

- ¿Están los parámetros de la rutina en un orden sensato, incluida la coincidencia del


orden de los parámetros en rutinas similares?

- ¿Están documentadas las suposiciones de interfaz?

- ¿La rutina tiene siete o menos parámetros?


- ¿Se utiliza cada parámetro de entrada?

- ¿Se utiliza cada parámetro de salida?

- ¿La rutina evita usar parámetros de entrada como variables de trabajo?

- Si la rutina es una función, ¿devuelve un valor válido en todas las


circunstancias posibles?
186 Capítulo 7: Rutinas de alta calidad

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.

- A veces, la operación que más se beneficia al ser puesta en una rutina


propia es simple.
- Puede clasificar las rutinas en varios tipos de cohesión, pero puede hacer que la mayoría de las
rutinas sean funcionalmente cohesivas, que es lo mejor.

- El nombre de una rutina es una indicación de su calidad. Si el nombre es incorrecto y es


preciso, es posible que la rutina esté mal diseñada. Si el nombre es incorrecto e inexacto, no le
está diciendo lo que hace el programa. De cualquier manera, un mal nombre significa que el
programa debe cambiarse.

- 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.1 Proteger su programa de entradas no válidas: página 188

- 8.2 Aserciones: página 189

- 8.3 Técnicas de manejo de errores: página 194

- 8.4 Excepciones: página 198

- 8.5 Bloquee su programa para contener el daño causado por los errores: página 203

- 8.6 Ayudas para la depuración: página 205

- 8.7 Determinar cuánta programación defensiva dejar en código de producción:


página 209

- 8.8 Estar a la defensiva sobre la programación defensiva: página 210

Temas relacionados

- Ocultación de información: "Ocultar secretos (Ocultar información)" en la Sección 5.3

- Diseño para el cambio: "Identificar áreas con probabilidad de cambio" en la Sección 5.3

- Arquitectura de software: Sección 3.5

- Diseño en la Construcción: Capítulo 5

- Depuración: Capítulo 23

La programación defensiva no significa estar a la defensiva con respecto a su programación: "¡Así


funciona!" La idea se basa en la conducción defensiva. En la conducción defensiva, adopta la
mentalidad de que nunca está seguro de lo que van a hacer los otros conductores. De esa manera, te
PUNTO CLAVE
aseguras de que si hacen algo peligroso no te lastimarán. Usted asume la responsabilidad de
protegerse incluso cuando podría ser culpa del otro conductor. En la programación defensiva, la idea
principal es que si a una rutina se le pasan datos incorrectos, no se dañará, incluso si los datos
incorrectos son culpa de otra rutina. En términos más generales, es el reconocimiento de que los
programas tendrán problemas y modificaciones, y que un programador inteligente desarrollará el
código en consecuencia.

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

8.1 Proteger su programa de entradas no válidas


En la escuela es posible que hayas escuchado la expresión "Basura entra, basura sale". Esa expresión es
esencialmente la versión del desarrollo de software de caveat emptor: que el usuario tenga cuidado.

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.

Hay tres formas generales de manejar la basura en:

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.

La programación defensiva es útil como complemento de otras técnicas de mejora de la calidad


descritas en este libro. La mejor forma de codificación defensiva es no insertar errores en primer
lugar. El uso de diseño iterativo, la escritura de pseudocódigo antes del código, la escritura de casos
de prueba antes de escribir el código y la realización de inspecciones de diseño de bajo nivel son
actividades que ayudan a evitar la inserción de defectos. Por lo tanto, se les debe dar una prioridad
más alta que la programación defensiva. Afortunadamente, puede utilizar la programación defensiva
en combinación con otras técnicas.

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

Ejemplo Java de una afirmación


afirmar denominador != 0 : "el denominador es inesperadamente igual a 0.";

Esta afirmación afirma quedenominadorno es igual a0. El primer argumento,denominador != 0, es


una expresión booleana que se evalúa comoverdaderoofalso. El segundo argumento es un mensaje
para imprimir si el primer argumento esfalso—es decir, si la afirmación es falsa.

Utilice aserciones para documentar suposiciones realizadas en el código y eliminar condiciones


inesperadas. Las aserciones se pueden usar para verificar suposiciones como estas:

- Que el valor de un parámetro de entrada se encuentre dentro de su rango esperado (o el valor de un


parámetro de salida lo hace)

- 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 un puntero no es nulo

- Que una matriz u otro contenedor pasado a una rutina puede contener al menosX número
de elementos de datos

- Que una tabla ha sido inicializada para contener valores reales

- 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

Construyendo su propio mecanismo de aserción


Referencia cruzadaLa Muchos lenguajes tienen soporte integrado para aserciones, incluidos C++, Java y Microsoft
construcción de su propia rutina
Visual Basic. Si su idioma no admite directamente las rutinas de aserción, son fáciles de
de afirmación es un buen ejemplo

de programación "en" un lenguaje


escribir. El C++ estándarafirmarmacro no proporciona mensajes de texto. He aquí un
en lugar de simplemente ejemplo de una mejoraAFIRMARimplementado como una macro de C++:
programar "en" un lenguaje. Para

obtener más detalles sobre esta

distinción, consulte la Sección Ejemplo en C++ de una macro de aserción


34.4, "Programe en su idioma, no # define ASSERT( condición, mensaje ) { if ( ! \
en él". (condición) ) { \
LogError("Afirmación fallida: ", \
# condición, mensaje); \
salir (EXIT_FAILURE); \
} \
}

Directrices para el uso de aserciones


Aquí hay algunas pautas para usar aserciones:

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

comentarios en lenguaje de programa.

Evite poner código ejecutable en asercionesPoner código en una aserción aumenta la


posibilidad de que el compilador elimine el código cuando desactive las aserciones.
Supongamos que tiene una afirmación como esta:
192 Capítulo 8: Programación defensiva

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

varias declaraciones en una línea.

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:

Ejemplo de Visual Basic de un uso seguro de una aserción


actionPerformed = RealizarAcción()
Debug.Assert( actionPerformed ) ' No se pudo realizar la acció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

Construcción de software orientada a


programas conocido como “diseño por contrato” (Meyer 1997). Cuando se utilizan condiciones
objetos(Mayer 1997). previas y posteriores, cada rutina o clase forma un contrato con el resto del programa.

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

' Condiciones previas


Debug.Assert (-90 <= latitud y latitud <= 90) Debug.Assert (0 <= longitud y
longitud <360) Debug.Assert (-500 <= elevación y elevación <= 75000)
8.2 Afirmaciones 193

...

' Postcondiciones
Debug.Assert (0 <= returnVelocity y returnVelocity <= 600)

' devolver valor


Velocidad = velocidad de retorno
Final Función

Si las variableslatitud,longitud, yelevaciónprovenían de una fuente externa, los valores no


válidos deben verificarse y manejarse mediante un código de manejo de errores en lugar de
aserciones. Sin embargo, si las variables provienen de una fuente interna confiable y el diseño
de la rutina se basa en la suposición de que estos valores estarán dentro de sus rangos
válidos, entonces las afirmaciones son apropiadas.

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

Sección 8.3, más adelante en este capítulo.


ambos. Algunos expertos argumentan que solo se necesita un tipo (Meyer 1997).

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

He aquí un ejemplo de cómo podría funcionar en elVelocidadejemplo:

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

' Condiciones previas


Aquí está el código de aserción. Debug.Assert (-90 <= latitud Y latitud <= 90) Debug.Assert (0 <= longitud Y
longitud <360) Debug.Assert (-500 <= elevación Y elevación <= 75000). . .

' 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

Si (longitud < 0) Entonces


longitud = 0
ElseIf (longitud > 360) Entonces. . .

8.3 Técnicas de manejo de errores


Las aserciones se utilizan para manejar errores que nunca deberían ocurrir en el código.
¿Cómo maneja los errores que espera que ocurran? Según las circunstancias específicas, es
posible que desee devolver un valor neutral, sustituir el siguiente dato válido, devolver la
misma respuesta que la vez anterior, sustituir el valor legal más cercano, registrar un mensaje
de advertencia en un archivo, devolver un código de error , llame a una rutina u objeto de
procesamiento de errores, muestre un mensaje de error o apague, o quizás quiera usar una
combinación de estas respuestas.

Aquí hay algunos detalles más sobre estas opciones:

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

Sustituya la siguiente pieza de datos válidosAl procesar un flujo de datos, algunas


circunstancias requieren simplemente devolver los siguientes datos válidos. Si está leyendo
registros de una base de datos y encuentra un registro corrupto, simplemente puede
continuar leyendo hasta que encuentre un registro válido. Si está tomando lecturas de un
termómetro 100 veces por segundo y no obtiene una lectura válida una vez, simplemente
espere otra centésima de segundo y tome la siguiente lectura.

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.

Registrar un mensaje de advertencia en un archivoCuando se detectan datos incorrectos, puede


optar por registrar un mensaje de advertencia en un archivo y luego continuar. Este enfoque se
puede utilizar junto con otras técnicas, como sustituir el valor legal más cercano o sustituir el
siguiente dato válido. Si usa un registro, considere si puede ponerlo a disposición del público de
manera segura o si necesita encriptarlo o protegerlo de alguna otra manera.

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:

- Establecer el valor de una variable de estado

- Estado de retorno como valor de retorno de la función

- Lanzar una excepción usando el mecanismo de excepción incorporado del lenguaje

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

Llamar a una rutina/objeto de procesamiento de erroresOtro enfoque es centralizar el manejo de


errores en una rutina de manejo de errores global o en un objeto de manejo de errores. La ventaja
de este enfoque es que la responsabilidad del procesamiento de errores se puede centralizar, lo que
puede facilitar la depuración. La contrapartida es que todo el programa conocerá esta capacidad
central y se acoplará a ella. Si alguna vez desea reutilizar parte del código del sistema en otro
sistema, tendrá que arrastrar la maquinaria de manejo de errores junto con el código que reutiliza.

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.

Mostrar un mensaje de error dondequiera que se encuentre el errorEste enfoque minimiza la


sobrecarga de manejo de errores; sin embargo, tiene el potencial de difundir mensajes de interfaz de
usuario a través de toda la aplicación, lo que puede crear desafíos cuando necesita crear una interfaz
de usuario consistente, cuando intenta separar claramente la interfaz de usuario del resto del
sistema o cuando intenta para localizar el software en un idioma diferente. Además, tenga cuidado
de decirle demasiado a un atacante potencial del sistema. Los atacantes a veces usan mensajes de
error para descubrir cómo atacar un sistema.

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.

Las aplicaciones de consumo tienden a favorecer la robustez frente a la corrección. Cualquier


resultado suele ser mejor que el apagado del software. El procesador de textos que estoy usando
ocasionalmente muestra una fracción de una línea de texto en la parte inferior de la pantalla. Si
detecta esa condición, ¿quiero que se apague el procesador de textos? No. Sé que la próxima vez que
presione Page Up o Page Down, la pantalla se actualizará y volverá a la normalidad.

Implicaciones de diseño de alto nivel del procesamiento de errores

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:

Tabla 8-1 Soporte de lenguaje popular para excepciones

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

Efecto de no atrapado invocastd::unex- Termina el hilo termina


excepción esperado(), que por de ejecución si programa
defecto invoca excepción es un
std::terminate(), “comprobado excepto
que por defecto ción”; ningún efecto si la
invocaabortar() excepción es una
“tiempo de ejecución

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,

consulte "Buena abstracción" en


rutina, al igual que los tipos de datos específicos.
Sección 6.2.
200 Capítulo 8: Programación defensiva

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. }
...
}

losObtenerIdTaxId()el código pasa el nivel inferiorExcepción EOFexcepción de vuelta a su


llamador. No se apropia de la excepción en sí misma; expone algunos detalles sobre cómo se
implementa al pasar la excepción de nivel inferior a la persona que llama. Esto acopla
efectivamente el código del cliente de la rutina no alEmpleadocódigo de la clase, sino al código
debajo de la Empleadoclase que lanza elExcepción EOFexcepción. La encapsulación se rompe y
la manejabilidad intelectual comienza a declinar.

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. ...
}

El código de manejo de excepciones dentroObtenerIdTaxId()probablemente solo mapeará el


io_disk_not_readyexcepción en elEmployeeDataNotAvailableexcepción, lo cual está bien porque es
suficiente para preservar la abstracción de la interfaz.

Incluya en el mensaje de excepción toda la información que condujo a la excepciónCada


excepción ocurre en circunstancias específicas que se detectan en el momento en que el código
genera la excepción. Esta información es invaluable para la persona que lee el mensaje de excepción.
Asegúrese de que el mensaje contenga la información necesaria para comprender por qué se lanzó
la excepción. Si la excepción se lanzó debido a una matriz
8.4 Excepciones 201

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.

Evitar vacíocapturabloquesA veces es tentador hacer pasar una excepción con la


que no sabe qué hacer, como esta:

Mal ejemplo de Java de ignorar una excepción


probar {
...
CODIFICACIÓN // mucho código
HORROR
...
} catch (excepción AnException) { }

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:

Buen ejemplo de Java para ignorar una excepción


probar {
...
// mucho código
...
} catch (excepción AnException) {
LogError("Excepción inesperada");
}

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.

Considere crear un informador de excepciones centralizadoUn enfoque para garantizar la


coherencia en el manejo de excepciones es utilizar un informador de excepciones centralizado.
El informador de excepciones centralizado proporciona un depósito central para conocer qué
tipos de excepciones existen, cómo se debe manejar cada excepción, el formato de los
mensajes de excepción, etc.
202 Capítulo 8: Programación defensiva

Aquí hay un ejemplo de un controlador de excepciones simple que simplemente imprime un mensaje de

diagnóstico:

Ejemplo de Visual Basic de un informador de excepciones centralizado, parte 1


Otras lecturasPara una Sub ReportException( _
explicación más detallada de ByVal className, _
esta técnica, véaseEstándares ByVal esta excepción como excepción _
prácticos para Microsoft Visual )
Basic .NET(Foxall 2003). Dim mensaje como cadena
Dim subtítulo como cadena

mensaje = "Excepción: " & thisException.Message & "." & ControlChars.CrLf & _
"Clase: " & className & ControlChars.CrLf & _
"Rutina:" & thisException.TargetSite.Name & ControlChars.CrLf caption = "Excepción"

MessageBox.Show (mensaje, título, MessageBoxButtons.OK, _


MessageBoxIcon.Exclamación )

Finalizar sub

Usaría este controlador de excepciones genérico con un código como este:

Ejemplo de Visual Basic de un informador de excepciones centralizado, parte 2


Probar

...
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.

Si decide crear un informador de excepciones centralizado, asegúrese de considerar los problemas


generales relacionados con el manejo centralizado de errores, que se analizan en "Llamar a una rutina/
objeto de procesamiento de errores" en la Sección 8.3.

Estandarice el uso de excepciones de su proyectoPara mantener el manejo de excepciones lo más


manejable intelectualmente posible, puede estandarizar el uso de excepciones de varias maneras:

- Si está trabajando en un lenguaje como C ++ que le permite lanzar una variedad


de tipos de objetos, datos y punteros, estandarice lo que lanzará específicamente.
Por compatibilidad con otros lenguajes, considere arrojar solo objetos derivados
delExcepciónclase básica.
8.5 Bloquee su programa para contener el daño causado por los errores 203

- 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.

- Determine si se utilizará un informador de excepciones centralizado.

- Defina si se permiten excepciones en constructores y destructores.

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).

8.5 Bloquee su programa para contener el daño causado por los


errores
Las barricadas son una estrategia de contención de daños. La razón es similar a la de tener
compartimentos aislados en el casco de un barco. Si el barco choca contra un iceberg y abre el casco,
ese compartimiento se cierra y el resto del barco no se ve afectado. También son similares a los
cortafuegos de un edificio. Los cortafuegos de un edificio evitan que el fuego se propague de una
parte del edificio a otra parte. (Las barricadas solían llamarse "cortafuegos", pero el término
"cortafuegos" ahora comúnmente se refiere al bloqueo del tráfico de red hostil).

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

204 Capítulo 8: Programación defensiva

área de validez, y responda con sensatez si los datos no son válidos. La figura 8-2 ilustra este
concepto.

Gráfico Interno Interno


Clase 1 Clase 2
Interfaz de usuario

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

Otro externo Interno Interno


objetos
Clase 11 Clasenorte

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

y desconfiado. armar la barricada. y de confianza

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.

Convierta los datos de entrada al tipo adecuado en el momento de la entradaLa entrada


normalmente llega en forma de cadena o número. A veces, el valor se asignará a un tipo booleano
como "sí" o "no". A veces, el valor se asignará a un tipo enumerado comoColor rojo, Color verde, y
Color azul. Llevar datos de tipo cuestionable durante cualquier período de tiempo en un programa
aumenta la complejidad y aumenta la posibilidad de que alguien pueda bloquear su programa
ingresando un color como "Sí". Convierta los datos de entrada a la forma adecuada tan pronto como
sea posible después de su entrada.
8.6 Ayudas para la depuración 205

Relación entre barricadas y afirmaciones


El uso de barricadas hace que la distinción entre aserciones y manejo de errores sea clara. Las
rutinas que están fuera de la barrera deben usar el manejo de errores porque no es seguro
hacer suposiciones sobre los datos. Las rutinas dentro de la barricada deben usar aserciones,
porque se supone que los datos que se les pasan deben desinfectarse antes de pasar a través
de la barricada. Si una de las rutinas dentro de la barricada detecta datos incorrectos, eso es
un error en el programa en lugar de un error en los datos.

El uso de barricadas también ilustra el valor de decidir a nivel arquitectónico cómo


manejar los errores. Decidir qué código está dentro y cuál está fuera de la barricada es
una decisión a nivel de arquitectura.

8.6 Ayudas para la depuració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.

No aplique automáticamente restricciones de producción a la


versión de desarrollo
Otras lecturasPara obtener más Un punto ciego común del programador es la suposición de que las limitaciones del software de
información sobre el uso del código de
producción se aplican a la versión de desarrollo. La versión de producción tiene que correr rápido. La
depuración para admitir la programación

defensiva, consulteEscribir código sólido


versión de desarrollo podría ejecutarse lentamente. La versión de producción tiene que ser tacaña
(Maguire 1993). con los recursos. Se puede permitir que la versión de desarrollo utilice los recursos de forma
extravagante. La versión de producción no debería exponer operaciones peligrosas al usuario. La
versión de desarrollo puede tener operaciones adicionales que puede usar sin una red de seguridad.

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.

Esté dispuesto a cambiar la velocidad y el uso de recursos durante el desarrollo a cambio de


herramientas integradas que pueden hacer que el desarrollo sea más fluido.

PUNTO CLAVE
206 Capítulo 8: Programación defensiva

Introducir ayudas para la depuración con anticipación

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.

Usar programación ofensiva


Referencia cruzadaPara obtener más Los casos excepcionales deben manejarse de manera que sean obvios durante el
detalles sobre el manejo de casos
desarrollo y recuperables cuando se ejecuta el código de producción. Michael Howard y
imprevistos, consulte "Consejos para

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.

- Asegúrese de que el código en cadacasodeclaracionesdefectoomásLa cláusula falla con fuerza


(aborta el programa) o es imposible pasarla por alto.

- Rellene un objeto con datos basura justo antes de que se elimine.

- 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.

Plan para eliminar las ayudas de depuració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.

Usar un preprocesador incorporadoSi su entorno de programación tiene un preprocesador como


C++, por ejemplo, puede incluir o excluir el código de depuración con solo tocar un botón del
compilador. Puede usar el preprocesador directamente o escribiendo una macro que funcione con
definiciones de preprocesador. Aquí hay un ejemplo de cómo escribir código usando el
preprocesador directamente:

Ejemplo de C++ del uso del preprocesador directamente para controlar el código de depuración

Para incluir el código de # definir DEPURAR


depuración, utilice#definirpara ...
definir el símboloDEPURAR. Para

excluir el código de depuración, # si está definido (DEBUG) //


no definaDEPURAR. 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(

Este código está incluido o declaración 1;


excluido, dependiendo de declaración 2;
ya seaDEPURARha sido ...
definido. declaración norte;

);
...
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

fuentes de información sobre cómo


designar el código de depuración y escriba su precompilador para seguir esa convención. Por
escribir uno propio, consulte ejemplo, en Java podría escribir un precompilador para responder a las palabras clave //
"Preprocesadores de macros" en la
#EMPEZAR DEPURAR y //#FIN DE DEPURACIÓN. Escriba un script para llamar al preprocesador
Sección 30.3.
y luego compile el código procesado. Ahorrará tiempo a largo plazo y no compilará por error el
código sin procesar.

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

individuales" en la Sección 22.5.


control regrese al llamador. Para el código de producción, puede reemplazar la rutina complicada
con una rutina auxiliar que simplemente devuelve el control inmediatamente a la persona que llama
o que realiza un par de operaciones rápidas antes de devolver el control. Este enfoque incurre en una
pequeña penalización de rendimiento y es una solución más rápida que escribir su propio
preprocesador. Mantenga las versiones de desarrollo y producción de las rutinas para que pueda
alternar durante el desarrollo y la producción futuros.

Puede comenzar con una rutina diseñada para verificar los punteros que se le pasan:

Ejemplo en C++ de una rutina que utiliza un código auxiliar de depuración


vacío hacer algo (
ALGÚN TIPO *puntero;
...
){

// comprobar los parámetros pasados


Esta línea llama a la rutina para en CheckPointer( pointer );
comprobar el puntero. ...

Durante el desarrollo, elpuntero de control()la rutina realizaría una verificación completa


en el puntero. Sería lento pero efectivo, y podría verse así:

Ejemplo en C++ de una rutina para verificar punteros durante el desarrollo


Esta rutina comprueba cualquier void CheckPointer( void *puntero ) {
puntero que se le haya pasado. Se // realizar la comprobación 1--quizás comprobar que no es NULL
puede utilizar durante el desarrollo // realizar la verificación 2, tal vez verificar que su placa de identificación sea legítima
para realizar tantas comprobaciones // realizar la comprobación 3, tal vez comprobar que lo que apunta no esté dañado. . .
como sea posible.

// realiza la comprobación n--...


}
8.7 Determinar cuánta programación defensiva dejar en el código de producción 209

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:

Ejemplo en C++ de una rutina para verificar punteros durante la producción


Esta rutina regresa inmediatamente a void CheckPointer( void *puntero ) {
la persona que llama. // sin código; solo regresa a la persona que llama
}

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.

8.7 Determinar cuánta programación defensiva dejar en el


código de producción
Una de las paradojas de la programación defensiva es que, durante el desarrollo, le gustaría que un
error se notara: preferiría que fuera desagradable que arriesgarse a pasarlo por alto. Pero durante la
producción, prefiere que el error sea lo más discreto posible, para que el programa se recupere o
falle correctamente. Aquí hay algunas pautas para decidir qué herramientas de programación
defensiva dejar en su código de producción y cuáles dejar de lado:

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.

8.8 Estar a la defensiva sobre la programación defensiva


Demasiado de cualquier cosa es Demasiada programación defensiva crea sus propios problemas. Si verifica los datos pasados como
malo, pero demasiado whisky es
parámetros de todas las formas imaginables en todos los lugares imaginables, su programa será
suficiente.

—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

cc2e.com/0868 LISTA DE VERIFICACIÓN: Programación defensiva


General
- ¿Se protege la rutina de datos de entrada incorrectos?

- ¿Ha utilizado afirmaciones para documentar suposiciones, incluidas las


condiciones previas y posteriores?

- ¿Se han utilizado afirmaciones solo para documentar condiciones que nunca deberían
ocurrir?

- ¿La arquitectura o el diseño de alto nivel especifican un conjunto específico de técnicas de


manejo de errores?

- ¿La arquitectura o el diseño de alto nivel especifican si el manejo de errores debe


favorecer la robustez o la corrección?

- ¿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 utilizado ayudas de depuración en el código?

- ¿Se han instalado las ayudas de depuración de tal manera que puedan
activarse o desactivarse sin mucho alboroto?

- ¿Es adecuada la cantidad de código de programación defensiva, ni demasiado


ni demasiado poco?

- ¿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?

- ¿Ha considerado alternativas al uso de una excepción?

- ¿El error se maneja localmente en lugar de lanzar una excepción no local, si es


posible?

- ¿Evita el código lanzar excepciones en constructores y destructores?

- ¿Están todas las excepciones en los niveles apropiados de abstracción para las rutinas
que las lanzan?

- ¿Cada excepción incluye toda la información relevante de antecedentes de la


excepción?

- ¿El código está libre de vacío?capturabloques? (O si un vacíocapturael bloque realmente es


apropiado, ¿está documentado?)
212 Capítulo 8: Programación defensiva

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?

- ¿Se comprueban todos los códigos de retorno de error?

- ¿Se detectan todas las excepciones?

- ¿Los mensajes de error evitan proporcionar información que ayudaría a un


atacante a entrar en el sistema?

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.

Stroustrup, Bjarne.El lenguaje de programación C++, 3d ed. Reading, MA: Addison-


Wesley, 1997. La sección 24.3.7.2 describe varias variaciones sobre el tema de
implementar aserciones en C++, incluida la relación entre aserciones y condiciones
previas y posteriores.

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

perjudiciales para el código de producción.

- Las aserciones pueden ayudar a detectar errores de manera temprana, especialmente en sistemas grandes, sistemas de alta

confiabilidad y bases de código que cambian rápidamente.

- La decisión sobre cómo manejar entradas incorrectas es una decisión clave para el manejo de errores y

una decisión clave de diseño de alto nivel.

- 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.

- Las restricciones que se aplican al sistema de producción no se aplican necesariamente


a la versión de desarrollo. Puede usar eso a su favor, agregando código a la versión de
desarrollo que ayuda a eliminar errores rápidamente.
Capítulo 9

El pseudocódigo
Proceso de programación
cc2e.com/0936 Contenido

- 9.1 Resumen de pasos en la construcción de clases y rutinas: página 216

- 9.2 Pseudocódigo para profesionales: página 218

- 9.3 Construcción de rutinas usando el PPP: página 220

- 9.4 Alternativas al PPP: página 232

Temas relacionados

- Creando clases de alta calidad: Capítulo 6

- Características de las rutinas de alta calidad: Capítulo 7

- Diseño en la Construcción: Capítulo 5

- Estilo de comentario: Capítulo 32

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.

Si es un programador experto, puede hojear este capítulo, pero mire el resumen de


pasos y revise los consejos para construir rutinas utilizando el Proceso de programación
de pseudocódigo en la Sección 9.3. Pocos programadores explotan todo el poder del
proceso y ofrece muchos beneficios.

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

9.1 Resumen de pasos en la construcción de clases y rutinas


La construcción de clases se puede abordar desde numerosas direcciones, pero generalmente es un proceso
iterativo de creación de un diseño general para la clase, enumeración de rutinas específicas dentro de la
clase, construcción de rutinas específicas y verificación de la construcción de la clase como un todo. Como
sugiere la Figura 9-1, la creación de clases puede ser un proceso complicado por todas las razones por las
que el diseño es un proceso complicado (razones que se describen en la Sección 5.1, “Desafíos de diseño”).

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í.

Pasos para crear una clase


Los pasos clave en la construcción de una clase son:

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.

Pasos en la construcción de una rutina

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

9.2 Pseudocódigo para profesionales

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.

- Evite los elementos sintácticos del lenguaje de programación de destino. El


pseudocódigo le permite diseñar a un nivel ligeramente superior al del propio código.
Cuando usa construcciones de lenguaje de programación, se hunde a un nivel inferior,
eliminando el beneficio principal del diseño en un nivel superior, y se carga con
restricciones sintácticas innecesarias.

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

comentarios" en la Sección 32.4.


- Escriba pseudocódigo a un nivel lo suficientemente bajo como para que la generación de código a
partir de él sea casi automática. Si el pseudocódigo está en un nivel demasiado alto, puede pasar por
alto detalles problemáticos en el código. Refine el pseudocódigo con más y más detalles hasta que
parezca que sería más fácil simplemente escribir el código.

Una vez que se escribe el pseudocódigo, construye el código a su alrededor y el pseudocódigo se


convierte en comentarios de lenguaje de programación. Esto elimina la mayor parte del esfuerzo de
comentar. Si el pseudocódigo sigue las pautas, los comentarios serán completos y significativos.

He aquí un ejemplo de un diseño en pseudocódigo que viola prácticamente todos los principios que
acabamos de describir:

Ejemplo de mal pseudocódigo


incrementar el número de recurso en 1 asignar una
estructura dlg usando malloc si malloc() devuelve
CODIFICACIÓN NULL y luego devuelve 1
HORROR
invoque OSrsrc_init para inicializar un recurso para el sistema operativo
* hRsrcPtr = número de recurso
devuelve 0

¿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.

Aquí hay un diseño para la misma operación en un pseudocódigo muy mejorado:

Ejemplo de buen pseudocódigo


Mantenga un registro de la cantidad actual de recursos en uso Si
hay otro recurso disponible
Asignar una estructura de cuadro de diálogo
Si se pudiera asignar una estructura de cuadro de diálogo
Tenga en cuenta que hay un recurso más en uso.
Inicialice el recurso.
Almacene el número de recurso en la ubicación proporcionada por la persona que llama
Endif
Terminara si

Retorna verdadero si se creó un nuevo recurso; de lo contrario devuelve falso

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.

- El pseudocódigo minimiza el esfuerzo de comentar. En el escenario típico de codificación,


usted escribe el código y luego agrega comentarios. En el PPP, las sentencias en pseudocódigo
se convierten en comentarios, por lo que en realidad lleva más trabajo eliminar los
comentarios que dejarlos.

- El pseudocódigo es más fácil de mantener que otras formas de documentación de


diseño. Con otros enfoques, el diseño está separado del código, y cuando uno cambia,
los dos no están de acuerdo. Con el PPP, las declaraciones en pseudocódigo se
convierten en comentarios en el código. Mientras se mantengan los comentarios en
línea, la documentación del diseño del pseudocódigo será precisa.

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.

9.3 Construcción de rutinas usando el PPP


Esta sección describe las actividades involucradas en la construcción de una rutina, a saber:

- Diseña la rutina.
- Codifica la rutina.

- Revisa el código.

- Limpia los cabos sueltos.

- Repita según sea necesario.

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():

ReportErrorMessage()toma un código de error como argumento de entrada y genera


un mensaje de error correspondiente al código. Es responsable de manejar los códigos
inválidos. Si el programa funciona de forma interactiva,ReportErrorMessage() muestra
el mensaje al usuario. Si está funcionando en modo de línea de comandos,
ReportErrorMessage()registra el mensaje en un archivo de mensajes. Después de
enviar el mensaje,ReportErrorMessage()devuelve un valor de estado, que indica si tuvo
éxito o no.

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

veces, cortar una vez: requisitos previos


estar seguro de que la rutina realmente es requerida, al menos indirectamente, por los requisitos del
ascendentes", y el Capítulo 4, "Decisiones proyecto.
clave de construcción".

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:

- La información que ocultará la rutina.

- 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

aserciones para documentar y


búferes llenos o vaciados, etc.)
verificar las condiciones previas y
- Las condiciones posteriores que la rutina garantiza que serán verdaderas antes de devolver el
posteriores" en la Sección 8.2.
control a la persona que llama (valores de salida dentro de rangos especificados, flujos inicializados,
archivos abiertos o cerrados, búferes llenos o vaciados, etc.)

Así es como se abordan estas preocupaciones en elReportErrorMessage()ejemplo:

- La rutina oculta dos hechos: el texto del mensaje de error y el método de


procesamiento actual (interactivo o línea de comando).

- No hay condiciones previas garantizadas para la rutina.

- La entrada a la rutina es un código de error.

- Se requieren dos tipos de salida: la primera es el mensaje de error y la segunda es


el estado queReportErrorMessage()vuelve a la rutina de llamada.

- La rutina garantiza que el valor de estado tendrá un valor deÉxitoo Falla.


222 Capítulo 9: El proceso de programación del pseudocódigo

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.

En el ejemplo,ReportErrorMessage()es inequívoco. Es un buen nombre.

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

primero, veaDesarrollo basado en


rutina de forma independiente.
pruebas: por
Ejemplo(Beck 2003). En el ejemplo, la entrada es simple, por lo que podría planear probarReportErrorMessage() con todos los
códigos de error válidos y una variedad de códigos no válidos.

Funcionalidad de investigación disponible en las bibliotecas estándarLa mejor manera de


mejorar tanto la calidad de su código como su productividad es reutilizar un buen código. Si se
encuentra luchando por diseñar una rutina que parece demasiado complicada, pregunte si
algunas o todas las funciones de la rutina ya están disponibles en el código de la biblioteca del
lenguaje, la plataforma o las herramientas que está utilizando. Pregunte si el código podría
estar disponible en la biblioteca de códigos mantenida por su empresa. Muchos algoritmos ya
han sido inventados, probados, discutidos en la literatura comercial, revisados y mejorados.
En lugar de perder el tiempo inventando algo cuando alguien ya ha escrito un doctorado.
disertación sobre él, tómese unos minutos para revisar el código que ya ha sido escrito y
asegúrese de que no está haciendo más trabajo del necesario.

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.

Piensa en la eficienciaDependiendo de su situación, puede abordar la eficiencia de una de dos


maneras. En la primera situación, en la gran mayoría de los sistemas, la eficiencia no es crítica. En tal
caso, asegúrese de que la interfaz de la rutina esté bien resumida y que su código sea legible para
que pueda mejorarlo más tarde si lo necesita. Si tiene una buena encapsulación, puede reemplazar
una implementación de lenguaje de alto nivel lenta y que acapara recursos con un mejor algoritmo o
una implementación de lenguaje de bajo nivel rápida y sencilla, y no afectará a ninguna otra rutina.
9.3 Construcción de rutinas usando el PPP 223

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

código", y el Capítulo 26, "Técnicas de


limitada, pocos identificadores disponibles, restricciones de tiempo ambiciosas o algún otro recurso
ajuste de código". escaso. La arquitectura debe indicar cuántos recursos puede usar cada rutina (o clase) y qué tan
rápido debe realizar sus operaciones.

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

pseudocódigo de la rutina. Para


para escribir el pseudocódigo; el pseudocódigo se usará en breve como base para el código del
obtener detalles sobre el diseño, lenguaje de programación.
consulte el Capítulo 5, "Diseño en la

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

Ejemplo de un comentario de encabezado para una rutina


Esta rutina genera un mensaje de error basado en un código de error proporcionado
por la rutina de llamada. La forma en que emite el mensaje depende del estado de
procesamiento actual, que recupera por sí solo. Devuelve un valor que indica éxito o
fracaso.

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:

Ejemplo de Pseudocódigo para una Rutina


Esta rutina genera un mensaje de error basado en un código de error proporcionado
por la rutina de llamada. La forma en que emite el mensaje depende del estado de
procesamiento actual, que recupera por sí solo. Devuelve un valor que indica éxito o
fracaso.

establecer el estado predeterminado en "fallido"


buscar el mensaje basado en el código de error

si el código de error es válido


si realiza un procesamiento interactivo, muestre el mensaje de error de forma
interactiva y declare el éxito

si está realizando un procesamiento de la línea de comandos, registre el mensaje de error en


la línea de comandos y declare el éxito

si el código de error no es válido, notificar al usuario que se ha detectado un error interno

información de estado de retorno

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,

una y otra vez”.


emocionalmente con su código y se vuelve más difícil deshacerse de un mal diseño y comenzar de nuevo.

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.

Empezar con pseudocódigo

Escribir la declaración de la rutina.

Escriba la primera y la última afirmación y gire


el pseudocódigo en comentarios de alto nivel

Repita según sea necesario

Completa el código debajo de cada comentario

Verifica el código

limpiar las sobras

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

Escribir la declaración de la rutina.Escriba la instrucción de interfaz de rutina: la declaración de


función en C++, la declaración de método en Java, la declaración de función o subprocedimiento en
Microsoft Visual Basic, o lo que su lenguaje requiera. Convierta el comentario de encabezado original
en un comentario de lenguaje de programación. Déjalo en posición sobre el pseudocódigo que ya
has escrito. Aquí están la instrucción de interfaz y el encabezado de la rutina de ejemplo en C++:

Ejemplo de C++ de una interfaz de rutina y encabezado agregado a pseudocódigo


Aquí está el comentario del /* Esta rutina genera un mensaje de error basado en un código de error proporcionado
encabezado que se ha convertido en por la rutina de llamada. La forma en que emite el mensaje depende del estado de
un comentario de estilo C++. procesamiento actual, que recupera por sí solo. Devuelve un valor que indica éxito o
fracaso.
*/

Aquí está la interfaz Informe de estado Mensaje de error (


declaración. ErrorCode errorToReport )

establecer el estado predeterminado en "fallido"


buscar el mensaje basado en el código de error

si el código de error es válido


si realiza un procesamiento interactivo, muestre el mensaje de error de forma
interactiva y declare el éxito

si está realizando un procesamiento de la línea de comandos, registre el mensaje de error en


la línea de comandos y declare el éxito

si el código de error no es válido, notificar al usuario que se ha detectado


un error interno

información de estado de retorno

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.

Convierte el pseudocódigo en comentarios de alto nivelMantenga la pelota rodando


escribiendo la primera y la última afirmación:{y}en C++. Luego convierta el pseudocódigo en
comentarios. Así es como se vería en el ejemplo:

Ejemplo de C++ de escritura de la primera y la última declaración en torno al pseudocódigo


/* Esta rutina genera un mensaje de error basado en un código de error proporcionado
por la rutina de llamada. La forma en que emite el mensaje depende del estado de
procesamiento actual, que recupera por sí solo. Devuelve un valor que indica éxito o
fracaso.
*/

Informe de estado Mensaje de error (


ErrorCode errorToReport ) {
9.3 Construcción de rutinas usando el PPP 227

Las declaraciones de pseudocódigo // establecer el estado predeterminado en "fallido"


de aquí abajo se han convertido en // busca el mensaje basado en el código de error // si el código
comentarios de C++. de error es válido
// si está realizando un procesamiento interactivo, muestra el mensaje de error // de
forma interactiva y declara el éxito

// si está procesando la línea de comandos, registre el mensaje de error en la // línea de


comandos y declare el éxito

// si el código de error no es válido, notificar al usuario que se ha detectado //


un error interno

// devuelve información de estado


}

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

una crítica de la aplicación de la


un esquema y luego escribe un párrafo para cada punto del esquema. Cada comentario de
metáfora de la escritura en general, pseudocódigo describe un bloque o párrafo de código. Al igual que la longitud de los párrafos
consulte "Caligrafía de software:
literarios, la longitud de los párrafos de código varía según el pensamiento que se exprese, y la
código de escritura" en la Sección 2.3.
calidad de los párrafos depende de la viveza y el enfoque de los pensamientos en ellos.

En este ejemplo, los dos primeros comentarios de pseudocódigo dan lugar a dos líneas de código:

Ejemplo de C++ de expresión de comentarios de pseudocódigo como código


/* Esta rutina genera un mensaje de error basado en un código de error proporcionado
por la rutina de llamada. La forma en que emite el mensaje depende del estado de
procesamiento actual, que recupera por sí solo. Devuelve un valor que indica éxito o
fracaso.
*/

Informe de estado Mensaje de error (


ErrorCode errorToReport ) {

// establecer el estado predeterminado en "fail" Status


Aquí está el código que se ha errorMessageStatus = Status_Failure;
rellenado.
// busca el mensaje basado en el código de error
Aquí está la nueva variable Mensaje errorMessage = LookupErrorMessage( errorToReport );
mensaje de error.
// si el código de error es válido
// si está realizando un procesamiento interactivo, muestra el mensaje de error // de
forma interactiva y declara el éxito

// si está procesando la línea de comandos, registre el mensaje de error en la // línea de


comandos y declare el éxito
228 Capítulo 9: El proceso de programación del pseudocódigo

// si el código de error no es válido, notificar al usuario que se ha detectado //


un error interno

// devuelve información de estado


}

Este es un comienzo en el código. La variablemensaje de errorse utiliza, por lo que debe


declararse. Si estuviera comentando después del hecho, dos líneas de comentarios para dos
líneas de código casi siempre serían una exageración. En este enfoque, sin embargo, lo
importante es el contenido semántico de los comentarios, no cuántas líneas de código
comentan. Los comentarios ya están allí y explican la intención del código, así que déjelos.

El código debajo de cada uno de los comentarios restantes debe completarse:

Ejemplo en C++ de una rutina completa creada con el proceso de


programación de pseudocódigo
/* Esta rutina genera un mensaje de error basado en un código de error proporcionado
por la rutina de llamada. La forma en que emite el mensaje depende del estado de
procesamiento actual, que recupera por sí solo. Devuelve un valor que indica éxito o
fracaso.
*/

Informe de estado Mensaje de error (


ErrorCode errorToReport ) {

// establecer el estado predeterminado en "fail" Status


errorMessageStatus = Status_Failure;

// busca el mensaje basado en el código de error


Mensaje errorMessage = LookupErrorMessage( errorToReport );

// si el código de error es válido if


El código de cada comentario ( errorMessage.ValidCode() ) {
se ha rellenado desde aquí // determinar el método de procesamiento
abajo. ProcessingMethod errorProcessingMethod = CurrentProcessingMethod();

// si está realizando un procesamiento interactivo, muestra el mensaje de error // de


forma interactiva y declara el éxito
if ( errorProcessingMethod == ProcessingMethod_Interactive ) {
DisplayInteractiveMessage( errorMessage.Text() );
errorMessageStatus = Estado_Éxito;
}

// si está procesando la línea de comandos, registre el mensaje de error en la // línea de


comandos y declare el éxito
Este código es un buen candidato else if ( errorProcessingMethod == ProcessingMethod_CommandLine ) {
para descomponerse aún más en Registro de mensajes de la línea de comandos;

una nueva rutina: if (mensajeRegistro.Estado() == CommandLineStatus_Ok) {


DisplayCommandLine- MessageLog.AddToMessageQueue( mensaje de error.Text() ); registro
Mensaje(). de mensajes.FlushMessageQueue();
errorMessageStatus = Estado_Éxito;
}
9.3 Construcción de rutinas usando el PPP 229

Este código y comentario son más {


nuevos y son el resultado de // no puedo hacer nada porque la rutina ya está procesando errores
desarrollar elsiprueba. }
más {
Este código y comentario también // no puedo hacer nada porque la rutina ya está procesando errores
son nuevos. }
}

// si el código de error no es válido, notificar al usuario que se ha detectado //


un error interno
más {
MostrarMensajeInteractivo(
"Error interno: código de error no válido en ReportErrorMessage()"
);
}

// devuelve información de estado


return errorMessageStatus;
}

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

requisitos, consulte el Capítulo 3,


implementación detallada. Un diseño que parece elegante en pseudocódigo puede volverse torpe en
"Mida dos veces, corte una vez: el lenguaje de implementación. Trabajar con la implementación detallada podría revelar un error en
requisitos previos aguas arriba".
la arquitectura, el diseño de alto nivel o los requisitos. Finalmente, el código podría tener un error de
codificación mestizo anticuado: ¡nadie es perfecto! Por todas estas razones, revise el código antes de
continuar.

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.

Aquí hay algunas pautas para aprovechar al máximo la compilación de su rutina:

- 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.

Recorra el código en el depuradorUna vez que se compila la rutina, colóquela en el


depurador y recorra cada línea de código. Asegúrese de que cada línea se ejecute como
espera. Puedes encontrar muchos errores siguiendo esta sencilla práctica.

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

“Construcción de andamios para probar clases


prueba, es decir, código que se usa para admitir rutinas mientras se prueban y que no se incluye en
individuales” en la Sección 22.5. el producto final. El scaffolding puede ser una rutina de arnés de prueba que llama a su rutina con
datos de prueba, o pueden ser stubs llamados por su rutina.

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”.

encuentra que una rutina es inusualmente defectuosa, comience de nuevo. No lo piratee,


reescríbalo. Los hacks generalmente indican una comprensión incompleta y garantizan errores tanto
ahora como más adelante. Vale la pena crear un diseño completamente nuevo para una rutina de
buggy. Pocas cosas son más satisfactorias que reescribir una rutina problemática y nunca encontrar
otro error en ella.
232 Capítulo 9: El proceso de programación del pseudocódigo

limpiar las sobras


Cuando haya terminado de revisar su código en busca de problemas, verifique las características
generales descritas a lo largo de este libro. Puede realizar varios pasos de limpieza para asegurarse
de que la calidad de la rutina esté a la altura de sus estándares:

- 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”.

- Compruebe las variables de la rutina. Compruebe si hay nombres de variables inexactos,


objetos no utilizados, variables no declaradas, objetos inicializados incorrectamente, etc. Para
obtener detalles, consulte los capítulos sobre el uso de variables, Capítulos 10 a 13.

- 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”.

- Verifique la documentación de la rutina. Asegúrese de que el pseudocódigo que se tradujo en


comentarios siga siendo exacto. Busque descripciones de algoritmos, documentación sobre
suposiciones de interfaz y dependencias no obvias, justificación de prácticas de codificación
poco claras, etc. Para obtener más información, consulte el Capítulo 32, “Código de
autodocumentación”.

- Eliminar comentarios redundantes. A veces, un comentario de pseudocódigo resulta ser redundante


con el código que describe el comentario, especialmente cuando el PPP se ha aplicado de forma
recursiva y el comentario solo precede a una llamada a una rutina bien nombrada.

Repita los pasos según sea necesario

Si la calidad de la rutina es mala, vuelva al pseudocódigo. La programación de alta


calidad es un proceso iterativo, así que no dude en repetir las actividades de
construcción nuevamente.

9.4 Alternativas al PPP


Por mi dinero, el PPP es el mejor método para crear clases y rutinas. Aquí hay algunos
enfoques diferentes recomendados por otros expertos. Puede utilizar estos enfoques
como alternativas o complementos del PPP.
9.4 Alternativas al PPP 233

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).

refactorizaciónLa refactorización es un enfoque de desarrollo en el que mejora el código a través de


una serie de transformaciones de preservación semántica. Los programadores usan patrones de
código incorrecto o "olores" para identificar secciones de código que necesitan mejorarse. El capítulo
24, “Refactorización”, describe este enfoque en detalle, y un buen libro sobre el tema es Martin
Fowler.Refactorización: mejora del diseño del código existente(Fowler 1999).

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).

¿Hackear?Algunos programadores intentan abrirse camino hacia el código de trabajo en lugar


de utilizar un enfoque sistemático como el PPP. Si alguna vez descubrió que se ha codificado en
una esquina en una rutina y tiene que comenzar de nuevo, eso es una indicación de que el PPP
podría funcionar mejor. Si pierde el hilo de sus pensamientos en medio de la codificación de
una rutina, esa es otra indicación de que el PPP sería beneficioso. ¿Alguna vez simplemente se
olvidó de escribir parte de una clase o parte de la rutina? Eso casi nunca sucede si estás usando
el PPP. Si te encuentras mirando la pantalla de la computadora sin saber por dónde empezar,
es una señal segura de que el PPP facilitará tu vida de programación.

cc2e.com/0943 LISTA DE VERIFICACIÓN: El proceso de programación del pseudocódigo


Referencia cruzadaEl objetivo de - ¿Ha comprobado que se han cumplido los requisitos previos?
esta lista es comprobar

si siguió un buen conjunto de pasos - ¿Ha definido el problema que resolverá la clase?
para crear una rutina. Para obtener

una lista de verificación que se centre


- ¿El diseño de alto nivel es lo suficientemente claro como para dar un buen nombre a la clase y a
en la calidad de la rutina en sí, cada una de sus rutinas?
consulte la lista de verificación

"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?

- ¿Ha buscado en los libros de referencia algoritmos útiles?


Traducido del inglés al español - www.onlinedoctranslator.com

234 Capítulo 9: El proceso de programación del pseudocódigo

- ¿Ha diseñado cada rutina utilizando un pseudocódigo detallado?

- ¿Has revisado mentalmente el pseudocódigo? ¿Es fácil de entender?

- ¿Ha prestado atención a las advertencias que lo remitirían al diseño (uso


de datos globales, operaciones que parecen más adecuadas para otra
clase u otra rutina, etc.)?

- ¿Tradujiste el pseudocódigo a código con precisión?

- ¿Aplicó el PPP recursivamente, dividiendo las rutinas en rutinas más pequeñas cuando
fue necesario?

- ¿Documentó las suposiciones a medida que las hacía?

- ¿Eliminó los comentarios que resultaron ser redundantes?

- ¿Ha elegido lo mejor de varias iteraciones, en lugar de simplemente detenerse


después de su primera iteración?

- ¿Entiendes completamente tu código? ¿Es fácil de entender?

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.

- Escribir un buen pseudocódigo requiere usar un inglés comprensible, evitar


características específicas de un solo lenguaje de programación y escribir al nivel de la
intención (describiendo lo que hace el diseño en lugar de cómo lo hará).

- El proceso de programación de pseudocódigo es una herramienta útil para el diseño detallado y


facilita la codificación. El pseudocódigo se traduce directamente en comentarios, lo que garantiza
que los comentarios sean precisos y útiles.

- No te conformes con el primer diseño que se te ocurra. Repita múltiples enfoques en


pseudocódigo y elija el mejor enfoque antes de comenzar a escribir código.

- 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:

Capítulo 10: Problemas generales en el uso de variables. . . . . . . . . . . . . . . . . . . . .


.237 Capítulo 11: El poder de los nombres de variables . . . . . . . . . . . . . . . . . . . . . . . .
.259 Capítulo 12: Tipos de datos fundamentales . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.291 Capítulo 13: Tipos de datos inusuales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .319
Capítulo 10

Problemas generales en el uso de


variables

cc2e.com/1085 Contenido

- 10.1 Alfabetización de datos: página 238

- 10.2 Cómo simplificar las declaraciones de variables: página 239

- 10.3 Pautas para inicializar variables: página 240

- 10.4 Alcance: página 244

- 10.5 Persistencia: página 251

- 10.6 Tiempo de enlace: página 252

- 10.7 Relación entre tipos de datos y estructuras de control: página 254

- 10.8 Uso de cada variable para exactamente un propósito: página 255

Temas relacionados

- Nombrar variables: Capítulo 11

- Tipos de datos fundamentales: Capítulo 12

- Tipos de datos inusuales: Capítulo 13

- Formateo de declaraciones de datos: "Diseño de declaraciones de datos" en la Sección 31.5

- Documentación de variables: “Declaraciones de datos de comentarios” en la Sección 32.5

Es normal y deseable que la construcción llene pequeños vacíos en los requisitos y la


arquitectura. Sería ineficiente dibujar planos a un nivel tan microscópico que cada detalle
estuviera completamente especificado. Este capítulo describe un problema de construcción de
tuercas y tornillos: los entresijos del uso de variables.

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.

10.1 Alfabetización de datos

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.

La prueba de alfabetización de datos

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.

______ tipo de datos abstracto ______literal


______ formación ______ variable local
______ mapa de bits ______ tabla de búsqueda

______ variable booleana ______ datos de miembros

______ árbol B ______ puntero


______ variable de carácter ______ privado
______ clase de contenedor ______ sinapsis retroactiva
______ precisión doble ______ integridad referencial
______ flujo alargado ______ pila
______ tipo enumerado ______ cuerda
______ punto flotante ______ variable estructurada

______ montón ______ árbol


índice ______ ______ definición de tipo

______ entero ______ Unión


______ lista enlazada ______ cadena de valor

______ constante nombrada ______ variante

______Puntaje total
10.2 Facilitando las declaraciones de variables 239

Así es como puede interpretar los puntajes (vagamente):

0–14 Eres un programador principiante, probablemente estés en tu primer año de informática en


la escuela o estés aprendiendo por ti mismo tu primer lenguaje de programación. Puede
aprender mucho leyendo uno de los libros enumerados en la siguiente subsección. Muchas
de las descripciones de técnicas en esta parte del libro están dirigidas a programadores
avanzados, y obtendrá más de ellos después de leer uno de estos libros.

15–19 Eres un programador intermedio o un programador experimentado que ha olvidado muchas


cosas. Aunque muchos de los conceptos le resultarán familiares, usted también puede beneficiarse
al leer uno de los libros que se enumeran a continuación.

20–24 Eres un experto programador. Probablemente ya tenga los libros enumerados a


continuación en su estantería.
25–29 Sabes más sobre tipos de datos que yo. Considere escribir su propio libro de
computadora. (¡Enviame una copia!)
30–32 Eres un fraude pomposo. Los términos "flujo alargado", "sinapsis retroactiva"
y "cadena de valor" no se refieren a tipos de datos, los inventé. ¡Lea la sección
"Honestidad intelectual" en el Capítulo 33, "Carácter personal"!

Recursos adicionales sobre tipos de datos


Estos libros son buenas fuentes de información sobre los tipos de datos:

Cormen, H. Thomas, Charles E. Leiserson, Ronald L. Rivest.Introducción a los Algoritmos.


Nueva York, NY: McGrawHill. 1990.

Sedgewick, Robert.Algoritmos en C++, Partes 1-4, 3d ed. Boston, MA: Addison-Wesley,


1998.

Sedgewick, Robert.Algoritmos en C++, Parte 5, 3d ed. Boston, MA: Addison-Wesley,


2002.

10.2 Facilitando las declaraciones de variables


Referencia cruzadaPara obtener detalles Esta sección describe lo que puede hacer para agilizar la tarea de declarar variables. Sin duda, esta
sobre el diseño de las declaraciones de
es una tarea pequeña, y usted podría pensar que es demasiado pequeña para merecer su propia
variables, consulte "Diseño de

declaraciones de datos" en la Sección 31.5.


sección en este libro. Sin embargo, pasa mucho tiempo creando variables, y desarrollar los hábitos
Para obtener detalles sobre cómo correctos puede ahorrar tiempo y frustración durante la vida de un proyecto.
documentarlos, consulte "Declaraciones

de datos de comentarios" en la Sección

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

La declaración implícita es una de las características más peligrosas disponibles en


cualquier idioma. Si programa en Visual Basic, sabe lo frustrante que es tratar de
descubrir por qué No. de cuentano tiene el valor correcto y luego observe que
numcuentaes la variable que se reinicializa a0. Este tipo de error es fácil de cometer si su
idioma no requiere que declare 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:

Desactivar declaraciones implícitasAlgunos compiladores le permiten deshabilitar


declaraciones implícitas. Por ejemplo, en Visual Basic usaría unOpción explícitainstrucción, que
le obliga a declarar todas las variables antes de usarlas.

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.

10.3 Pautas para inicializar variables


La inicialización incorrecta de datos es una de las fuentes de error más fértiles en la programación de
computadoras. El desarrollo de técnicas efectivas para evitar problemas de inicialización puede ahorrar
mucho tiempo de depuración.
PUNTO CLAVE

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

"Pruebas de flujo de datos" en la


- El valor de la variable está desactualizado. A la variable se le asignó un valor en algún
Sección 22.3.
momento, pero el valor ya no es válido.

- A una parte de la variable se le ha asignado un valor ya otra parte no.


10.3 Pautas para inicializar variables 241

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.

Las siguientes son pautas para evitar problemas de inicialización:

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.

Ejemplo de C++ de inicialización en el momento de la declaración


float CalificacionesEstudiantes[ MAX_ESTUDIANTES ] = { 0.0 };

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

obtener detalles sobre la


estilos de codificación como el siguiente, en el que las declaraciones se agrupan y luego las
programación defensiva, consulte el inicializaciones se agrupan, todo lejos del primer uso real de las variables.
Capítulo 8, “Programación defensiva”.

Ejemplo de Visual Basic de mala inicialización


' declara todas las variables Dim
accountIndex As Integer Dim total As
CODIFICACIÓN Double
HORROR
Dim hecho como booleano

' inicializa todas las variables


índiceCuenta =0
total = 0,0
hecho = Falso
...

' código usando accountIndex . . .

' código usando total . . .

' código usando hecho


Mientras no está hecho
...
242 Capítulo 10: Problemas generales en el uso de variables

Una mejor práctica es inicializar las variables lo más cerca posible de donde se usaron por primera vez:

Ejemplo de Visual Basic de buena inicialización


Dim índice de cuenta como número
entero índice de cuenta = 0
' código usando accountIndex . . .

Dim total As Doble total =


totalse declara e inicializa 0.0
cerca de donde se usa. ' código usando total . . .

Dim hecho Como


hechotambién se declara e booleano hecho = Falso
inicializa cerca de donde se ' código usando hecho
usa. Mientras no está hecho
...

El segundo ejemplo es superior al primero por varias razones. En el momento en que la


ejecución del primer ejemplo llega al código que usahecho,hechopodría haber sido
modificado. Si ese no es el caso cuando escribe el programa por primera vez, las
modificaciones posteriores podrían hacerlo así. Otro problema con el primer enfoque es que
juntar todas las inicializaciones crea la impresión de que todas las variables se usan a lo largo
de toda la rutina, cuando en realidadhechose usa solo al final. Finalmente, a medida que se
modifica el programa (como lo será, aunque solo sea mediante depuración), se pueden
generar bucles alrededor del código que usa hecho, yhechoserá necesario reinicializar. El
código del segundo ejemplo requerirá pocas modificaciones en tal caso. El código del primer
ejemplo es más propenso a producir un molesto error de inicialización.

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:

Ejemplo Java de buena inicialización


int índiceCuenta = 0; // código
usando accountIndex . . .
10.3 Pautas para inicializar variables 243

totalse inicializa cerca de total doble = 0,0; // código


donde se usa. usando total. . .

hechotambién se inicializa cerca booleano hecho = falso; //


de donde se usa. código usando done while ( !
done ) {
...

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

14.2, “Declaraciones cuyo orden no


palabras clave son útiles para definir constantes de clase, parámetros de solo entrada y cualquier
importa”. variable local cuyos valores estén destinados a permanecer sin cambios después de la inicialización.

Preste especial atención a los contadores y acumuladores.las variablesi,j,k,suma, y totalson a


menudo contadores o acumuladores. Un error común es olvidarse de reiniciar un contador o un
acumulador antes de la próxima vez que se use.

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.

Comprobar la necesidad de reinicializaciónPregúntese si alguna vez será necesario


reinicializar la variable, ya sea porque un bucle en la rutina usa la variable muchas veces o
porque la variable conserva su valor entre llamadas a la rutina y debe restablecerse entre
llamadas. Si necesita reinicializarse, asegúrese de que la declaración de inicialización esté
dentro de la parte del código que se repite.

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.

Aprovecha los mensajes de advertencia de tu compiladorMuchos compiladores le advierten que


está utilizando una variable no inicializada.
244 Capítulo 10: Problemas generales en el uso de variables

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

Sección 8.1, “Proteger su programa de


que los valores sean razonables.
entradas no válidas”, y el resto del

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.

Inicialice la memoria de trabajo al comienzo de su programaInicializar la memoria de trabajo a


un valor conocido ayuda a exponer los problemas de inicialización. Puede tomar cualquiera de varios
enfoques:

- 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).

Las siguientes secciones proporcionan pautas que se aplican al alcance.

Localizar referencias a variables


El código entre las referencias a una variable es una "ventana de vulnerabilidad". En la
ventana, se puede agregar un nuevo código, alterando la variable sin darse cuenta, o alguien
que lea el código puede olvidar el valor que se supone que contiene la variable. Siempre es
una buena idea localizar las referencias a las variables manteniéndolas juntas.

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:

Ejemplo de Java de intervalo variable


a = 0;
b = 0;
C = 0;
a = b + c;

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:

Ejemplo de Java de tramos de uno y cero


a = 0;
b = 0;
C = 0;
b = a + 1;
b = b / c;

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

modelos de ingeniería de software


(Conte, Dunsmore y Shen 1986).
El lapso promedio se calcula promediando los lapsos individuales. En el segundo ejemplo, para
b, (1+0)/2 equivale a un intervalo medio de 0,5. Cuando mantiene las referencias a las variables
juntas, permite que la persona que lee su código se concentre en una sección a la vez. Si las
referencias están muy separadas, obliga al lector a saltar en el programa. Por lo tanto, la
principal ventaja de mantener juntas las referencias a las variables es que mejora la legibilidad
del programa.
246 Capítulo 10: Problemas generales en el uso de variables

Mantenga las variables "vivas" durante el menor tiempo posible

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.

Larga vida Larga vida


tiempo tiempo

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.

Medición del tiempo de vida de una variable

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:

Ejemplo de Java de variables con tiempos de vida excesivamente largos


1 // inicializa todas las variables
2 recordIndex = 0;
3 total = 0;
4 hecho = falso;
...
26 while (índiceRegistro <registroCuenta) {. . .
27
última referencia arecordIndex. 28 índiceRegistro = índiceRegistro + 1; . . .

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:

recordIndex ( línea 28 - línea 2 + 1 ) = 27


total ( línea 69 - línea 3 + 1 ) = 67
hecho ( línea 70 - línea 4 + 1 ) = 67
Tiempo promedio en vivo ( 27 + 67 + 67 ) / 3≈54

El ejemplo se ha reescrito a continuación para que las referencias de las variables estén más
juntas:

Ejemplo Java de variables con buenos tiempos de vida cortos


...
Inicialización derecordIndex se 25 índiceregistro = 0;
mueve hacia abajo desde la línea 3. 26 while (índiceRegistro <registroCuenta) {. . .
27
28 índiceRegistro = índiceRegistro + 1; . . .

Inicialización detotaly hechose 62 total = 0;


mueven hacia abajo de las 63 hecho = falso;
líneas 4 y 5. 64 mientras (! hecho) {
...
69 if ( total > total proyectado ) {
70 hecho = verdadero;

Estos son los tiempos en vivo para las variables en este ejemplo:

recordIndex ( línea 28 - línea 25 + 1 ) = 4


total ( línea 69 - línea 62 + 1 ) = 8
hecho ( línea 70 - línea 63 + 1 ) = 8
Tiempo promedio en vivo ( 4 + 8 + 8 ) / 3≈7

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

Pautas generales para minimizar el alcance


Aquí hay algunas pautas específicas que puede usar para minimizar el alcance:

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,

consulte la Sección 10.3, “Pautas para


que cuando modifique el ciclo, recuerde realizar las modificaciones correspondientes a la
inicializar variables”, anteriormente inicialización del ciclo. Más tarde, cuando modifique el programa y coloque otro ciclo alrededor
en este capítulo.
del ciclo inicial, la inicialización funcionará en cada paso a través del nuevo ciclo en lugar de
solo en el primer paso.

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,

consulte "Idealmente, declare y defina


más pueda hacer para aclarar dónde una variable recibe su valor, mejor. Los lenguajes como C++ y
cada variable cerca de donde se usa por Java admiten inicializaciones de variables como estas:
primera vez" en la Sección 10.3.

Ejemplo de C++ de buenas declaraciones e inicializaciones de variables


int reciboÍndice = 0;
float RecibosDiarios = RecibosHoy();
double totalReceipts = TotalReceipts( dailyReceipts );

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

Sección 14.2, “Declaraciones cuyo orden no


fáciles de localizar. El primer ejemplo ilustra la violación de este principio:
importa”.

Ejemplo de C++ del uso de dos conjuntos de variables de forma confusa


void SummarizeData(...) {
...
Declaraciones que utilizan dos GetOldData( oldData, &numOldData ); ObtenerNuevosDatos(nuevosDatos,
conjuntos de variables. &numNuevoDatos); totalDatosViejos = Sum(datosViejos, numDatosViejos);
totalNewData = Sum( newData, numNewData );
PrintOldDataSummary( oldData, totalOldData, numOldData );
PrintNewDataSummary( newData, totalNewData, numNewData );
SaveOldDataSummary( totalOldData, numOldData );
SaveNewDataSummary( totalNewData, numNewData );

...
}

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,

consulte la Sección 13.3, “Datos 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.

Comentarios sobre la minimización del alcance

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.

Para obtener más información,


tendrá que tener en cuenta en un momento dado. Cuanto menos tenga que tener en cuenta,
consulte "Ocultar secretos (ocultar menor será la posibilidad de que cometa un error porque olvidó uno de los muchos detalles que
información)" en la Sección 5.3.
necesitaba recordar.

La diferencia entre la filosofía de la "conveniencia" y la filosofía de la "manejabilidad


intelectual" se reduce a una diferencia de énfasis entre escribir programas y leerlos. De hecho,
maximizar el alcance puede hacer que los programas sean fáciles de escribir, pero un
PUNTO CLAVE
programa en el que cualquier rutina puede usar cualquier variable en cualquier momento es
más difícil de entender que un programa que usa rutinas bien factorizadas. En tal programa,
no puedes entender solo una rutina; debe comprender todas las demás rutinas con las que
esa rutina comparte datos globales. Dichos programas son difíciles de leer, difíciles de depurar
y difíciles de modificar.

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

en lugar de datos globales” en


rutina, excelente. Si no puede limitar el alcance a una rutina, restrinja la visibilidad a las rutinas
la Sección 13.3. en una sola clase. Si no puede restringir el alcance de la variable a la clase que es más
responsable de la variable, cree rutinas de acceso para compartir los datos de la variable con
otras clases. Descubrirá que rara vez, si alguna vez, necesita usar datos globales desnudos.

10.5 Persistencia
"Persistencia" es otra palabra para la vida útil de una pieza de datos. La persistencia toma varias
formas. Algunas variables persisten

- durante la vida de un bloque particular de código o rutina. Variables declaradas dentro de un


por loop en C++ o Java son ejemplos de este tipo de persistencia.

- 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

descripción, al igual queestáticoVariables en C++ y Java.

- 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!

10.6 Tiempo de vinculación

Un tema de inicialización con implicaciones de gran alcance para el mantenimiento y la


modificabilidad del programa es el "tiempo de enlace": el momento en el que la variable y su
valor se unen (Thimbleby 1988). ¿Están unidos cuando se escribe el código? ¿Cuándo está
compilado? ¿Cuándo está cargado? ¿Cuándo se ejecuta el programa? ¿En otro momento?

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

BarraTítulo.color = 0xFF; // 0xFF es un valor hexadecimal para el color azul

El valor0xFFestá ligado a la variabletitleBar.coloren el momento en que se escribe el código


porque0xFFes un valor literal codificado en el programa. Codificar de esta manera es casi
siempre una mala idea porque si esto0xFFcambios, puede salirse de sincronía con0xFFse usa
en otra parte del código que debe tener el mismo valor que este.

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

Ejemplo de Java de una variable que está vinculada en tiempo de compilación

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.

Aquí hay un ejemplo de vinculación posterior, en tiempo de ejecución:

Ejemplo de Java de una variable que está vinculada 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 codificación (uso de números mágicos)

- Tiempo de compilación (uso de una constante con nombre)

- 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.

10.7 Relación entre tipos de datos y estructuras de


control
Los tipos de datos y las estructuras de control se relacionan entre sí de maneras bien definidas que
fueron descritas originalmente por el científico informático británico Michael Jackson (Jackson 1975).
Esta sección esboza la relación regular entre los datos y el flujo de control.

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.

Figura 10-4Los datos iterativos se repiten.

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.

10.8 Usando cada variable para exactamente un propósito


Es posible usar variables para más de un propósito de varias maneras sutiles. Estás
mejor sin este tipo de sutileza.

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 ); . . .

// intercambiar las raíces


temp = raíz[0];
raíz[0] = raíz[1];
raíz[1] = temperatura;

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

sobre el uso de parámetros de rutina,


casos hace que parezca que están relacionados cuando no lo están. La creación de variables únicas
consulte la Sección 7.5, “Cómo usar los para cada propósito hace que su código sea más legible. Aquí hay una mejora:
parámetros de rutina”.

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 ); . . .

// intercambiar las raíces


oldRoot = raíz[0];
raíz[0] = raíz[1];
raíz[1] = antiguaRaíz;

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:

- El valor de la variablenúmero de páginaspodría representar el número de páginas impresas, a


menos que sea igual-1, en cuyo caso indica que se ha producido un error.

CODIFICACIÓN - La variableIdentificación del clientepodría representar un número de cliente, a menos que su


HORROR
valor sea mayor que500,000, en cuyo caso se resta500,000para obtener el número de una
cuenta morosa.

- La variablebytesEscritopodría ser el número de bytes escritos en un archivo de salida, a


menos que su valor sea negativo, en cuyo caso indica el número de la unidad de disco
utilizada para la salida.
10.8 Usando cada variable para exactamente un propósito 257

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.

cc2e.com/1092 LISTA DE VERIFICACIÓN: Consideraciones generales en el uso de datos


Referencia cruzadaPara Inicializar variables
lista de verificación que se aplica a tipos

específicos de datos en lugar de


- ¿Cada rutina verifica la validez de los parámetros de entrada?
problemas generales, consulte la lista de
- ¿El código declara variables cerca de donde se usaron por primera vez?
verificación en el Capítulo 12, “Tipos de

datos fundamentales”, en la página 316.


- ¿El código inicializa las variables a medida que se declaran, si es posible?
Para problemas relacionados con la

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

288. - ¿Se inicializan correctamente los contadores y acumuladores y, si es necesario, se


reinicializan cada vez que se utilizan?

- ¿Las variables se reinicializan correctamente en el código que se ejecuta repetidamente?

- ¿Se compila el código sin advertencias del compilador? (¿Y has activado
todas las advertencias disponibles?)

- Si su idioma usa declaraciones implícitas, ¿ha compensado los


problemas que causan?

Otros problemas generales en el uso de datos

- ¿Todas las variables tienen el menor alcance posible?

- ¿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?

- ¿Las estructuras de control corresponden a los tipos de datos?


258 Capítulo 10: Problemas generales en el uso de variables

- ¿Se están utilizando todas las variables declaradas?

- ¿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?

- ¿Cada variable tiene un único propósito?


- ¿El significado de cada variable es explícito, sin significados ocultos?

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.

- El enlace anticipado tiende a limitar la flexibilidad pero minimiza la complejidad. La vinculación


tardía tiende a aumentar la flexibilidad, pero al precio de una mayor complejidad.

- Utilice cada variable para un solo propósito.


Capítulo 11

El poder de los nombres de variables


cc2e.com/1184 Contenido

- 11.1 Consideraciones en la elección de buenos nombres: página 259

- 11.2 Nombrar tipos específicos de datos: página 264

- 11.3 El poder de las convenciones de nomenclatura: página 270

- 11.4 Convenciones de nombres informales: página 272

- 11.5 Prefijos estandarizados: página 279

- 11.6 Crear nombres cortos que sean legibles: página 282

- 11.7 Tipos de nombres a evitar: página 285

Temas relacionados

- Nombres de rutinas: Sección 7.3

- Nombres de clase: Sección 6.2

- Cuestiones generales en el uso de variables: Capítulo 10

- Formateo de declaraciones de datos: "Diseño de declaraciones de datos" en la Sección 31.5

- Documentación de variables: “Declaraciones de datos de comentarios” en la Sección 32.5

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”.

11.1 Consideraciones en la elección de buenos nombres


No puede dar un nombre a una variable de la misma manera que le da un nombre a un perro, porque es lindo o
tiene un buen sonido. A diferencia del perro y su nombre, que son entidades diferentes, una variable y el nombre de
una variable son esencialmente lo mismo. En consecuencia, la bondad o maldad de una variable está determinada
en gran medida por su nombre. Elija los nombres de las variables con cuidado.

259
260 Capítulo 11: El poder de los nombres de variables

Aquí hay un ejemplo de código que usa nombres de variables incorrectos:

Ejemplo de Java de nombres de variables deficientes

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:

Ejemplo de Java de buenos nombres de variables


saldo = saldo - ultimoPago;
mensualTotal = nuevasCompras + Impuesto sobre las Ventas( nuevasCompras ); saldo =
saldo + Cargo por pago atrasado (ID de cliente, saldo) + total mensual; saldo = saldo +
Intereses( IDcliente, saldo );

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.

La consideración de nomenclatura más importante

La consideración más importante al nombrar una variable es que el nombre describa de


manera completa y precisa la entidad que representa la variable. Una técnica eficaz para dar
con un buen nombre es expresar con palabras lo que representa la variable. A menudo, esa
PUNTO CLAVE
declaración en sí misma es el mejor nombre de variable. Es fácil de leer porque no contiene
abreviaturas crípticas y es inequívoco. Debido a que es una descripción completa de la entidad,
no se confundirá con otra cosa. Y es fácil de recordar porque el nombre es similar al concepto.

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.

La tabla 11-1 muestra varios ejemplos de nombres de variables, buenos y malos:

Tabla 11-1 Ejemplos de nombres de variables buenos y malos

buenos nombres, malos nombres,


Propósito de la variable buenos descriptores Descriptores pobres

Total acumulado de Total en ejecución,comprobarTotal escrito,Connecticut,cheques,CHKTTL,X,


cheques emitidos a la fecha x1,x2

Velocidad de un tren velocidad,trenVelocidad, terciopelo,v,televisión,X,x1,x2,tren


bala velocidad en mph

Fecha actual fecha actual,Líneas de fecha de discos compactos,Actual,C,X,x1,x2,

Líneas por página hoy por página fecha lpp,líneas,yo,X,x1,x2

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

son malos nombres.


PUNTO CLAVE

Orientación al Problema

Un buen nombre mnemotécnico generalmente habla del problema en lugar de la solución. Un


buen nombre tiende a expresar laquémás que elcómo. En general, si un nombre se refiere a
algún aspecto de la computación en lugar del problema, es uncómopreferible aqué. Evite ese
nombre en favor de un nombre que se refiera al problema mismo.

Un registro de datos de empleados podría llamarseentradaRecoempleadoDatos.entradaReces un término


informático que se refiere a la computación de ideas: entrada y registro.empleadoDatosse refiere al dominio
del problema en lugar del universo informático. De manera similar, para un campo de bits que indica el
estado de la impresora,bitBanderaes un nombre más informático queimpresoraListo. En una aplicación de
contabilidad,calcVales más informático quesuma.
262 Capítulo 11: El poder de los nombres de variables

Longitud óptima del nombre

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

Demasiado largo: numberOfPeopleOnTheUsOlympicTeam


numberOfSeatsInTheStadium
número máximo de puntos en los Juegos Olímpicos modernos n,notario

Demasiado corto: público,ntm

norte,ns,nsisd

metro,diputado,máximo,puntos

Solo bien: numTeamMiembros,teamMemberCount

numSeatsInStadium,número de asientos

teamPointsMax,puntosRegistrar

El efecto del alcance en los nombres de variables

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. . .

subsistema de base de datos de espacio de nombres {

...
// muchas declaraciones. . .

Si declaras unEmpleadoclase en ambosSubsistema de interfaz de usuarioy elSubsistema de


base de datos, puede identificar a cuál desea hacer referencia escribiendoSubsistema de
interfaz de usuario:: empleadooBase de datosSubsistema::Empleado. En Java, puede lograr lo
mismo usando paquetes.

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.

Calificadores de valor calculado en nombres de variables

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

264 Capítulo 11: El poder de los nombres de variables

me gustalos ingresos totales,gastototal,Promedio de ingresos, ygastopromediono apela al sentido


del orden. Finalmente, la consistencia mejora la legibilidad y facilita el mantenimiento.

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.

Opuestos comunes en nombres de variables


Referencia cruzadaPara obtener una Usa los opuestos con precisión. El uso de convenciones de nomenclatura para los opuestos ayuda a la
lista similar de opuestos en nombres
coherencia, lo que ayuda a la legibilidad. Pares comoempezar/finalizarson fáciles de entender y recordar.
de rutinas, consulte "Usar opuestos

con precisión" en la Sección 7.3.


Los pares que se apartan de los opuestos del lenguaje común tienden a ser difíciles de recordar y, por lo
tanto, son confusos. Aquí hay algunos opuestos comunes:

- empezar/finalizar

- primero último

- bloqueado/desbloqueado

- mínimo máximo

- siguiente anterior

- viejo nuevo

- abierto/cerrado

- visible invisible

- origen Destino

- origen Destino
- arriba abajo

11.2 Nombrar tipos específicos de datos


Además de las consideraciones generales al nombrar datos, surgen consideraciones especiales
al nombrar tipos específicos de datos. Esta sección describe consideraciones específicas para
variables de bucle, variables de estado, variables temporales, variables booleanas, tipos
enumerados y constantes con nombre.
11.2 Nombrar tipos específicos de datos 265

Índices de bucle de nombres

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”.

Ejemplo de Java de un nombre de variable de bucle simple


for ( i = primerelemento; i <últimoelemento; i++ ) {
datos[ i ] = 0;
}

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:

Ejemplo de Java de un buen nombre de variable de bucle descriptivo


cuentaregistro = 0;
while ( másPuntuaciones() ) {
puntuación[recordCount] = GetNextScore();
registroCuenta++;
}

// líneas usando recordCount. . .

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.

Ejemplo de Java de buenos nombres de bucle en un bucle anidado


for (índiceequipo = 0; índiceequipo < cuentaequipo; índiceequipo++ ) {
for (índice de eventos = 0; índice de eventos < número de eventos [índice de equipo]; índice de eventos ++) {
score[ teamIndex ][ eventIndex ] = 0;
}
}

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

Nombrar variables de estado

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:

Ejemplos de C++ de banderas crípticas


si (bandera)...
if (statusFlag & 0x0F)... if (printFlag ==
CODIFICACIÓN 16)... if (computeFlag == 0)...
HORROR

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:

Ejemplos de C++ de un mejor uso de las variables de estado


si (datosListos)...
if ( characterType & PRINTABLE_CHAR ) ... if ( reportType
== ReportType_Annual ) ... if ( recalcNeeded = false ) ...

listo para datos = verdadero;

tipo de caracter = CARÁCTER_CONTROL;


tipo de informe = ReportType_Anual;
recalcNeeded = falso;

Claramente,tipo de carácter = CONTROL_CHARACTERes más significativo quebandera de estado =


0x80. Asimismo, el condicionalif ( tipo de informe == tipo de informe_anual )es más claro quesi
( imprimir Bandera == 16 ). El segundo ejemplo muestra que puede usar este enfoque con tipos
enumerados, así como con constantes con nombre predefinidas. Así es como podría usar constantes
con nombre y tipos enumerados para configurar los valores usados en el ejemplo:

Declaración de variables de estado en C++


// valores para CharacterType const int
LETTER = 0x01; const int DIGITO = 0x02;
const int PUNTUACIÓN = 0x04; const int
LINE_DRAW = 0x08;
11.2 Nombrar tipos específicos de datos 267

const int PRINTABLE_CHAR = ( LETRA | DÍGITO | PUNTUACIÓN | LINE_DRAW );

const int CONTROL_CHARACTER = 0x80;

// valores para ReportType enum


ReportType {
ReportType_Daily,
ReportType_Mensual,
ReportType_Quarterly,
ReportType_Anual,
ReportType_All
};

Cuando se encuentre "descifrando" una sección de código, considere cambiar el nombre de


las variables. Está bien descifrar misterios de asesinatos, pero no debería necesitar descifrar el
código. Deberías poder leerlo.

Nombrar variables temporales


Las variables temporales se utilizan para contener resultados intermedios de cálculos, como marcadores de
posición temporales y para contener valores de mantenimiento. Suelen llamarsetemperatura,X, o algún otro
nombre vago y no descriptivo. En general, las variables temporales son una señal de que el programador
aún no comprende completamente el problema. Además, debido a que a las variables se les otorga
oficialmente un estado "temporal", los programadores tienden a tratarlas de manera más casual que a otras
variables, lo que aumenta la posibilidad de errores.

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:

Ejemplo en C++ de un nombre de variable "temporal" no informativo


// Calcular soluciones de una ecuación cuadrática. // Esto
supone que (b^2-4*a*c) es positivo. temperatura = sqrt(b^2 -
4*a*c);
solucion[0] = ( -b + temp ) / ( 2 * a ); solucion[1] = ( -b -
temp ) / ( 2 * a );

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

Este es esencialmente el mismo código, pero mejorado con el uso de un nombre de


variable preciso y descriptivo.

Nombrar variables booleanas


Las siguientes son algunas pautas para usar al nombrar variables booleanas:

Tenga en cuenta los nombres booleanos típicosAquí hay algunos nombres de variables booleanas particularmente
útiles:

- hechoUsarhechopara indicar si se ha hecho algo. La variable puede indicar si se


realiza un bucle o se realiza alguna otra operación. Establecerhechoafalso antes
de que se haga algo, y configúrelo enverdaderocuando algo se completa.

- errorUsarerrorpara indicar que se ha producido un error. Establezca la variable enfalso


cuando no ha ocurrido ningún error y paraverdaderocuando ha ocurrido un error.

- fundarUsarfundarpara indicar si se ha encontrado un valor. Establecerfundarafalso cuando no se ha


encontrado el valor y paraverdaderouna vez que se ha encontrado el valor. Usarfundaral buscar un
valor en una matriz, un ID de empleado en un archivo, una lista de cheques de pago para un monto
de cheque de pago determinado, etc.

- éxitooOKUsaréxitooOKpara indicar si una operación ha tenido éxito. Establezca la


variable enfalsocuando una operación ha fallado y paraverdaderocuando una operación
ha tenido éxito. Si puedes, reemplazaéxitocon un nombre más específico que describe
con precisión lo que significa tener éxito. Si el programa tiene éxito cuando se completa
el procesamiento, puede usarprocesamientoCompletadoen cambio. Si el programa tiene
éxito cuando se encuentra un valor, puede utilizarfundaren cambio.

Dar nombres de variables booleanas que impliquen verdadero o falsonombres comohecho


yéxito son buenos nombres booleanos porque el estado esverdaderoofalso; algo se hace o no
se hace; es un éxito o no lo es. nombres comoestadoyarchivo fuente, por otro lado, son
nombres booleanos pobres porque obviamente no sonverdaderoofalso. ¿Qué significa si
estadoesverdadero? ¿Significa que algo tiene un estado? Todo tiene un estatus. Lo hace
verdaderosignifica que el estado de algo está bien? o hacefalsosignifica que nada ha ido mal?
Con un nombre comoestado, no se puede decir.

Para obtener mejores resultados, reemplaceestadocon un nombre comoerroroestadoOKy reemplazararchivo fuente

confuenteArchivoDisponibleofuenteArchivoEncontrado, o lo que sea que represente la variable.

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

Use nombres de variables booleanos positivosnombres negativos comoextraviado,no


hecho, y Sin éxitoson difíciles de leer cuando se niegan, por ejemplo,

si no no encontrado

Tal nombre debe ser reemplazado porfundar,hecho, oprocesamientoCompletadoy luego


negado con un operador según corresponda. Si lo que buscas se encuentra, tienes fundaren
vez deno no encontrado.

Nombrar tipos enumerados


Referencia cruzadaPara obtener Cuando usa un tipo enumerado, puede asegurarse de que está claro que todos los miembros del
detalles sobre el uso de tipos
tipo pertenecen al mismo grupo usando un prefijo de grupo, comoColor_,Planeta_, oMes_. Aquí hay
enumerados, consulte la Sección

12.6, “Tipos enumerados”.


algunos ejemplos de elementos de identificación de tipos enumerados usando prefijos:

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

Planeta de enumeración pública


Planeta Tierra
Planeta Marte
Planeta_Venus
Enumeración final

Mes de enumeración pública


Mes_Enero
Mes_febrero
...
Mes_diciembre
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,

consulte la Sección 12.7, “Constantes con

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.

11.3 El poder de las convenciones de nomenclatura


Algunos programadores se resisten a los estándares y las convenciones, y con razón. Algunos
estándares y convenciones son rígidos e ineficaces, destructivos para la creatividad y la calidad del
programa. Esto es desafortunado ya que los estándares efectivos son algunas de las herramientas
más poderosas a su disposición. Esta sección analiza por qué, cuándo y cómo debe crear sus propios
estándares para nombrar variables.

¿Por qué tener convenciones?

Las convenciones ofrecen varios beneficios específicos:

- 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.

- Le ayudan a transferir conocimientos entre proyectos. Las similitudes en los nombres le


brindan una comprensión más fácil y segura de lo que se supone que deben hacer las
variables desconocidas.

- Le ayudan a aprender a codificar más rápidamente en un nuevo proyecto. En lugar de


aprender que el código de Anita se ve así, el de Julia es así y el de Kristin es diferente, puede
trabajar con un conjunto de código más coherente.

- Reducen la proliferación de nombres. Sin convenciones de nomenclatura, puede llamar


fácilmente a la misma cosa con dos nombres diferentes. Por ejemplo, puede llamar puntos
totales tantopuntoTotalypuntos totales. Esto puede no ser confuso para ti cuando escribes el
código, pero puede ser enormemente confuso para un nuevo programador que lo lea más
tarde.

- 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.

Cuándo debería tener una convención de nomenclatura

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 varios programadores están trabajando en un proyecto

- Cuando planea entregar un programa a otro programador para


modificaciones y mantenimiento (que es casi siempre)

- Cuando sus programas son revisados por otros programadores en su organización

- 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

Siempre te beneficias de tener algún tipo de convención de nomenclatura. Las consideraciones


anteriores deberían ayudarlo a determinar el alcance de la convención para usar en un proyecto
en particular.

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

el Capítulo 27, “Cómo el tamaño del


siguiente sección. En general, el grado de formalidad que necesita depende de la cantidad de
programa afecta la construcción”. personas que trabajan en un programa, el tamaño del programa y la vida útil esperada del
programa. En proyectos pequeños y descartables, una convención estricta podría ser una sobrecarga
innecesaria. En proyectos más grandes en los que participan varias personas, ya sea inicialmente o
durante la vida útil del programa, las convenciones formales son una ayuda indispensable para la
legibilidad.
272 Capítulo 11: El poder de los nombres de variables

11.4 Convenciones de nombres informales


La mayoría de los proyectos utilizan convenciones de nomenclatura relativamente informales, como las que se describen en

esta sección.

Directrices para una convención independiente del idioma


Aquí hay algunas pautas para crear una convención independiente del idioma:

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().

Diferenciar entre clases y objetos.La correspondencia entre nombres de clases y nombres


de objetos, o entre tipos y variables de esos tipos, puede ser complicada. Existen varias
opciones estándar, como se muestra en los siguientes ejemplos:

Opción 1: Diferenciación de Tipos y Variables mediante Capitalización Inicial


Widget de widgets;
Widget más largo Widget más largo;

Opción 2: diferenciación de tipos y variables en mayúsculas


Widget widget;
WIDGET MÁS LARGO

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

los prefijos por motivos estéticos.

La opción 4 a veces se usa como alternativa a la opción 3, pero tiene el inconveniente de


alterar el nombre de cada instancia de una clase en lugar de solo el nombre de una clase.

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..

Identificar elementos de tipos enumeradosLos elementos de los tipos enumerados deben


identificarse por las mismas razones que las constantes con nombre: para que sea fácil saber que el
nombre es para un tipo enumerado en lugar de una variable, una constante con nombre o una
función. Se aplica el enfoque estándar: puede usar mayúsculas o unami_oMI_prefijo para el nombre
del tipo en sí y use un prefijo basado en el tipo específico comoColor_oPlaneta_ para los miembros
del tipo.

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

Directrices para convenciones específicas del idioma


Siga las convenciones de nomenclatura del idioma que está utilizando. Puede encontrar libros para
la mayoría de los idiomas que describen pautas de estilo. En las siguientes secciones se
proporcionan pautas para C, C++, Java y Visual Basic.

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

esDirectrices de programación en - Cychson variables de carácter.


C(ciruela 1984).
- iyjson índices enteros.
- nortees un número de algo.

- pagses un puntero.

- ses una cadena.

- Las macros del preprocesador están enTODAS_MAYÚSCULAS. Esto generalmente se extiende para incluir

también typedefs.

- Los nombres de variables y rutinas están entodo en minúsculas.

- El carácter de subrayado (_) se utiliza como separador:letras_en_minúsculases más


legible queletras en minúsculas.

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

programación de C++, consulteLos


- iyjson índices enteros.
elementos del estilo C++(Misfeldt,

Bumgardner y Gray 2004). - pagses un puntero.

- Constantes, typedefs y macros de preprocesador están enTODAS_MAYÚSCULAS.

- Los nombres de clases y otros tipos están enmayúsculas y minúsculas mixtas().

- Los nombres de variables y funciones 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, 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

iyjson índices enteros.


elementos del estilo Java, 2ª ed.
-
(Vermeulen et al. 2000).

- Las constantes están enTODAS_MAYÚSCULASseparados por guiones bajos.

- 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.

- losobteneryestablecerLos prefijos se utilizan para los métodos de acceso.

Convenciones de Visual Basic

Visual Basic realmente no ha establecido convenciones firmes. La siguiente sección recomienda


una convención para Visual Basic.

Consideraciones de programación de lenguaje mixto


Al programar en un entorno de lenguaje mixto, las convenciones de nomenclatura (así como las
convenciones de formato, las convenciones de documentación y otras convenciones) se pueden
optimizar para lograr una coherencia y legibilidad generales, incluso si eso significa ir en contra de la
convención de uno de los lenguajes que forma parte de la mezcla.

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.

Ejemplos de convenciones de nomenclatura

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 contenido de la variable (lo que representa)

- El tipo de datos (constante con nombre, variable primitiva, tipo definido por el usuario o clase)

- El alcance de la variable (privada, clase, paquete o global)

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.

Tabla 11-3 Convenciones de nomenclatura de muestra para C++ y Java

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.

Tipos enumerados Además de la regla anterior, los tipos enumerados


siempre se expresan en forma plural.
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_.

g_variable global Las variables globales tienen el prefijogramo_. Las constantes

CONSTANTE con nombre están enTODAS_MAYÚSCULAS. Las macros están en

MACRO TODAS_MAYÚSCULAS.

Base_EnumeratedType Los tipos enumerados tienen un prefijo mnemotécnico para su


tipo base indicado en singular; por ejemplo,Color rojo, Color
azul.
278 Capítulo 11: El poder de los nombres de variables

Tabla 11-4 Ejemplos de convenciones de nomenclatura para C

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.

f_FileStaticVariable Las variables de módulo (archivo) tienen el prefijoF_.

G_GLOBAL_Variable global Las variables globales tienen el prefijoGRAMO_y un


mnemotécnico del módulo (archivo) que define la variable en
mayúsculas, por ejemplo,PANTALLA_Dimensiones.
LOCAL_CONSTANTE Las constantes con nombre que son privadas para una sola rutina
o módulo (archivo) están en mayúsculas, por ejemplo, FILAS_MAX
.

G_CONSTANTE GLOBAL Las constantes globales con nombre están en mayúsculas y


tienen el prefijoGRAMO_y un mnemotécnico del módulo
(archivo) que define la constante nombrada en mayúsculas, por
ejemplo,G_SCREEN_ROWS_MAX.
LOCALMACRO() Las definiciones de macro que son privadas para una sola rutina o
módulo (archivo) están en mayúsculas.

G_GLOBAL_MACRO() Las definiciones de macros globales están en mayúsculas y


tienen el prefijoGRAMO_y un mnemotécnico del módulo
(archivo) que define la macro en mayúsculas, por ejemplo,
G_SCREEN_LOCATION().

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.

Tabla 11-5 Convenciones de nomenclatura de muestra para Visual Basic

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.

T_TipoNombre 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 y unT_prefijo.

T_tipos enumerados Además de la regla anterior, los tipos enumerados


siempre se expresan en forma plural.
11.5 Prefijos estandarizados 279

Tabla 11-5 Convenciones de nomenclatura de muestra para Visual Basic

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_.

g_variable global Las variables globales tienen el prefijogramo_. Las

CONSTANTE constantes con nombre están enTODAS_MAYÚSCULAS.

Base_EnumeratedType Los tipos enumerados tienen un prefijo mnemotécnico para su


tipo base indicado en singular; por ejemplo,Color rojo, Color
azul.

11.5 Prefijos estandarizados


Otras lecturasPara obtener más La estandarización de prefijos para significados comunes proporciona un enfoque conciso pero
detalles sobre la convención de
coherente y legible para nombrar datos. El esquema más conocido para estandarizar prefijos es la
nomenclatura húngara, consulte

“La revolución húngara” (Simonyi


convención de nomenclatura húngara, que es un conjunto de pautas detalladas para nombrar
y Heller 1991). variables y rutinas (¡no húngaras!) que se usó ampliamente en un momento en la programación de
Microsoft Windows. Aunque la convención de nomenclatura húngara ya no es de uso generalizado,
la idea básica de estandarizar en abreviaturas concisas y precisas sigue teniendo valor.

Los prefijos estandarizados se componen de dos partes: la abreviatura de tipo definido por el usuario
(UDT) y el prefijo semántico.

Abreviaturas de tipo definidas por el usuario

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

Tabla 11-6 Ejemplo de UDT para un procesador de textos

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:

CH chPosición del Cursor;


RCS espacio de trabajo de usuario scr;

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.

Tabla 11-7 Prefijos semánticos

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

ultimo El último elemento que debe tratarse en una matriz.ultimoes la contrapartida de


primero.
11.5 Prefijos estandarizados 281

Tabla 11-7 Prefijos semánticos

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.

metro Variable de nivel de clase

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.

min El primer elemento absoluto en una matriz u otro tipo de lista.


pags Puntero

Los prefijos semánticos se formatean en minúsculas o en una combinación de mayúsculas y


minúsculas y se combinan con los UDT y con otros prefijos semánticos según sea necesario.
Por ejemplo, el primer párrafo de un documento se llamaríaPensilvaniapara mostrar que es un
párrafo yprimeropara mostrar que es el primer párrafo:primeroPa. Un índice en el conjunto de
párrafos se llamaríaiPa;cPaes la cuenta, o el número de párrafos; y primerPaActivoDocumento
ylastPaActiveDocumentson el primer y último párrafo del documento activo actual.

Ventajas de los prefijos estandarizados


Los prefijos estandarizados le brindan todas las ventajas generales de tener una convención de
nomenclatura, así como varias otras ventajas. Debido a que muchos nombres son estándar, tiene
menos nombres para recordar en cualquier programa o clase.
PUNTO CLAVE

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.

El principal escollo con los prefijos estandarizados es que un programador no le da a la variable


un nombre significativo además de su prefijo. Siipadesigna inequívocamente un índice en una
serie de párrafos, es tentador no hacer que el nombre sea más significativo como
ipaActiveDocument. Para facilitar la lectura, cierre el ciclo y proponga un nombre descriptivo.
282 Capítulo 11: El poder de los nombres de variables

11.6 Crear nombres cortos que sean legibles


El deseo de utilizar nombres de variables cortos es, en cierto modo, un remanente de una época anterior de
la informática. Los lenguajes más antiguos como ensamblador, Basic genérico y Fortran limitaban los
nombres de variables a 2–8 caracteres y obligaban a los programadores a crear nombres cortos. La
PUNTO CLAVE
computación temprana estaba más estrechamente relacionada con las matemáticas y su uso de términos
comoi,j, yk como las variables en sumatorias y otras ecuaciones. En lenguajes modernos como C++, Java y
Visual Basic, puede crear nombres de prácticamente cualquier longitud; casi no tiene motivos para acortar
nombres significativos.

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.

Directrices generales sobre abreviaturas

Aquí hay varias pautas para crear abreviaturas. Algunos de ellos contradicen a otros, así que
no intentes usarlos todos al mismo tiempo.

- Utilice abreviaturas estándar (las de uso común, que se enumeran en un


diccionario).

- Eliminar todas las vocales no principales. (computadorase conviertecmptr, ypantallase convierte pantalla.
manzanase convierteaplicación, yenterose convierteintegral.)

- Eliminar artículos:y,o,la, y así.


- Use la primera letra o las primeras letras de cada palabra.

- Trunca consistentemente después de la primera, segunda o tercera (la que sea apropiada)
letra de cada palabra.

- Mantenga la primera y la última letra de cada palabra.

- Utilice todas las palabras significativas del nombre, hasta un máximo de tres palabras.

- Eliminar sufijos inútiles—En g,educar, y así.

- Mantenga el sonido más notable en cada sílaba.

- Asegúrese de no cambiar el significado de la variable.

- 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:

ILV2SK8 XMEQWK S2DTM8O nxtc TRMN8R

Comentarios sobre abreviaturas


Puede caer en varias trampas al crear abreviaturas. Aquí hay algunas reglas para evitar
trampas:

No abrevie eliminando un carácter de una palabraEscribir un carácter es poco trabajo


adicional, y el ahorro de un carácter difícilmente justifica la pérdida de legibilidad. Es
como los calendarios que tienen "junio" y "julio". Tienes que tener mucha prisa para
deletrear junio como "Jun". Con la mayoría de las eliminaciones de una letra, es difícil
recordar si eliminó el carácter. Elimina más de un carácter o deletrea la palabra.

Abreviar consistentementeUtilice siempre la misma abreviatura. Por ejemplo, usenúmero en todas


partes oNoen todas partes, pero no use ambos. Del mismo modo, no abrevie una palabra en
algunos nombres y no en otros. Por ejemplo, no utilice la palabra completaNúmeroen algunos
lugares y la abreviaturanúmeroen otros.

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).

Evite combinaciones que resulten en mala lectura o mala pronunciaciónPara


referirse al final deB, favorENDBsobreCURVA. Si utiliza una buena técnica de separación,
no necesitará esta guía ya queCURVA,Curva, ocurvano se pronunciará mal.

Use un diccionario de sinónimos para resolver colisiones de nombresUn problema en la creación de


nombres cortos son las colisiones de nombres, nombres que se abrevian a la misma cosa. Por ejemplo, si
está limitado a tres caracteres y necesita usarencendidoydesembolso total de ingresosen la misma área de
un programa, puede abreviar inadvertidamente ambos afrd.

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:

Fortran Ejemplo de una buena tabla de traducción


C ***************************************************************
C * * * Traducción Mesa
C
C Variable Sentido
C -------- -------
C XPOS Posición de la coordenada x (en metros)
C YPOS Posición de la coordenada Y (en metros)
C NDSCMP Necesita computación (=0 si no se necesita computación;
C =1 si se necesita cálculo)
C PTGTTL Punto grandioso Total
C PTVLMX Punto Valor Máximo
C PSCRMX Posible Puntaje Máximo
C ****************************************************************

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:

- Un lector del código podría no entender la abreviatura.


- Otros programadores pueden usar varias abreviaturas para referirse a la misma palabra, lo
que crea una confusión innecesaria.

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

control de sion, ingresando la abreviatura y volviendo a verificarlaes algo bueno.


Significa que no se creará una abreviatura a menos que sea tan común que valga la pena
documentarla.

Este enfoque aborda el segundo riesgo al reducir la probabilidad de que un


programador cree una abreviatura redundante. Un programador que quiera
abreviar algo revisará el documento de abreviaturas e ingresará la nueva
abreviatura. Si ya existe una abreviatura para la palabra que el programador quiere
abreviar, el programador lo notará y luego usará la abreviatura existente en lugar
de crear una nueva.

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.

11.7 Tipos de nombres a evitar


Aquí hay algunas pautas con respecto a los nombres de variables para evitar:

Evite nombres o abreviaturas engañosasAsegúrese de que un nombre no sea ambiguo. Por


ejemplo,FALSOsuele ser lo contrario deCIERTOy sería una mala abreviatura para "Temporada
de Higos y Almendras".

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

"distancia psicológica". Para obtener


abreviaturas. Evite nombres comoclienteRecsyRepresentantes de clientes. Son solo una letra
más información, consulte “Cómo diferente entre sí, y la letra es difícil de notar. Tenga al menos dos letras de diferencia entre los
puede ayudar la 'distancia
nombres, o ponga las diferencias al principio o al final.Registros de clientesy Informes de clientesson
psicológica'” en la Sección 23.4.
mejores que los nombres originales.
286 Capítulo 11: El poder de los nombres de variables

Evite nombres que suenen similares, comoenvolveryrapLos homónimos se interponen cuando


tratas de discutir tu código con otros. Una de las cosas que más me molestan de la programación
extrema (Beck 2000) es el uso excesivamente inteligente de los términos Goal Donor y Gold Owner,
que son prácticamente indistinguibles cuando se pronuncian. Terminas teniendo conversaciones
como esta:

Acabo de hablar con el donante de la meta—

¿Dijiste "Propietario de oro" o "Donante de objetivos"?

Dije "Objetivo Donante".

¿Qué?

GOL - - - ¡DONANTE!

OK, donante de objetivos. No tienes que gritar, Goll' Maldita sea.

¿Dijiste "rosquilla dorada"?

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?

Evite las palabras que comúnmente se escriben mal en inglésAusencia,acumular,acsend,


calandrar,concebir,diferido,definir,independencia,ocasionalmente,preferido,recibo,reemplazar, y
muchos otros son errores ortográficos comunes en inglés. La mayoría de los manuales de inglés
contienen una lista de palabras mal escritas. Evite usar tales palabras en sus nombres de variables.

No diferencie nombres de variables únicamente por mayúsculasSi está programando en un


lenguaje que distingue entre mayúsculas y minúsculas, como C++, puede sentirse tentado a usarfrd
porencendido,FRDpor deber de revisión final, yFrdpordesembolso total de ingresos. Evita esta
práctica. Aunque los nombres son únicos, la asociación de cada uno con un significado particular es
arbitraria y confusa.Frdpodría asociarse fácilmente condeber de revisión finalyFRDcon desembolso
total de ingresos, y ninguna regla lógica le ayudará a usted ni a nadie más a recordar cuál es cuál.
11.7 Tipos de nombres a evitar 287

Evite múltiples lenguajes naturalesEn proyectos multinacionales, imponga el uso de un único


lenguaje natural para todo el código, incluidos los nombres de clases, nombres de variables, etc. Leer
el código de otro programador puede ser un desafío; leer el código de otro programador en el
sureste de Marte es imposible.

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-

kling nombres comomargaretypookiea lo largo de su programa virtualmente garantiza que nadie


más podrá entenderlo. Evite el nombre de su novio, el nombre de su esposa, el nombre de su
cerveza favorita u otros nombres ingeniosos (también conocidos como tontos) para las variables, a
menos que el programa sea realmente sobre su novio, esposa o cerveza favorita. Incluso entonces,
sería prudente reconocer que cada uno de estos podría cambiar y que, por lo tanto, los nombres
genéricosnovio,esposa, ycerveza favoritason superiores!

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:

gráfico de ojos optogramaI gráfico de ojos

TTLCONFUSIÓN TTLCONFUSIÓN TTLC0NFUSION


difícil2Leer hardZRead difícil2Leer
GRAN TOTAL GRAN TOTAL 6TOTALALEATORIO

ttl5 ttlS ttlS

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

257 en el Capítulo 10, “Cuestiones


resultado fue que los científicos calcularon mal la trayectoria de una nave espacial y perdieron una sonda
generales sobre el uso de variables”. espacial, por una suma de $ 1.6 mil millones (Weinberg 1983).
288 Capítulo 11: El poder de los nombres de variables

cc2e.com/1191 LISTA DE VERIFICACIÓN: Nombrar variables

Consideraciones generales de nomenclatura

- ¿El nombre describe de forma completa y precisa lo que representa la variable?

- ¿El nombre se refiere al problema del mundo real en lugar de a la solución del
lenguaje de programación?

- ¿El nombre es lo suficientemente largo como para no tener que descifrarlo?

- ¿Hay calificadores de valor calculado, si los hay, al final del nombre?

- ¿Utiliza el nombreContaroÍndiceen vez denúmero?

Nombrar tipos específicos de datos


- ¿Son significativos los nombres de los índices de bucle (algo que no seai,j, oksi
el bucle tiene más de una o dos líneas o está anidado)?

- ¿Todas las variables "temporales" han sido renombradas a algo más


significativo?

- ¿Se nombran las variables booleanas de modo que sus significados cuando severdadero¿están

claros?

- ¿Los nombres de tipo enumerado incluyen un prefijo o sufijo que indica la


categoría, por ejemplo,Color_porColor rojo,Color verde,Color azul, ¿y así?

- ¿Las constantes nombradas se nombran por las entidades abstractas que representan en lugar de

los números a los que se refieren?

Convenciones de nombres
- ¿La convención distingue entre datos locales, de clase y globales?

- ¿Distingue la convención entre nombres de tipo, constantes con nombre,


tipos enumerados y variables?

- ¿Identifica la convención parámetros de solo entrada para rutinas en


lenguajes que no los aplican?

- ¿Es la convención lo más compatible posible con las convenciones estándar


del idioma?

- ¿Los nombres están formateados para facilitar la lectura?

Nombres cortos
- ¿El código usa nombres largos (a menos que sea necesario usar nombres cortos)?

- ¿Evita el código abreviaturas que guardan un solo carácter?

- ¿Todas las palabras se abrevian consistentemente?


Puntos clave 289

- ¿Los nombres son pronunciables?

- ¿Se evitan los nombres que podrían leerse mal o pronunciarse mal?

- ¿Se documentan los nombres cortos en las tablas de traducción?

Problemas comunes de nomenclatura: ¿Ha evitado...


- . . . nombres que son engañosos?

- . . . nombres con significados similares?

- . . . ¿Nombres que se diferencian sólo por uno o dos caracteres?

- . . . ¿nombres que suenen parecidos?

- . . . nombres que usan numerales?

- . . . nombres intencionalmente mal escritos para hacerlos más cortos?

- . . . nombres que comúnmente se escriben mal en inglés?

- . . . nombres que entran en conflicto con nombres de rutinas de biblioteca estándar o con

nombres de variables predefinidos?

- . . . nombres totalmente arbitrarios?

- . . . caracteres difíciles de leer?

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.

- Las convenciones de nomenclatura distinguen entre datos locales, de clase y globales.


Distinguen entre nombres de tipo, constantes con nombre, tipos enumerados y variables.

- 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

conveniencia del tiempo de lectura sobre la conveniencia del tiempo de escritura.


Capítulo 12

Tipos de datos fundamentales


cc2e.com/1278 Contenido

- 12.1 Números en General: página 292

- 12.2 Números enteros: página 293

- 12.3 Números de coma flotante: página 295

- 12.4 Caracteres y cadenas: página 297

- 12.5 Variables booleanas: página 301

- 12.6 Tipos enumerados: página 303

- 12.7 Constantes con nombre: página 307

- 12.8 Matrices: página 310

- 12.9 Creación de sus propios tipos (aliasing de tipos): página 311

Temas relacionados

- Nombrando datos: Capítulo 11

- Tipos de datos inusuales: Capítulo 13

- Cuestiones generales en el uso de variables: Capítulo 10

- Formateo de declaraciones de datos: "Diseño de declaraciones de datos" en la Sección 31.5

- Documentación de variables: “Declaraciones de datos de comentarios” en la Sección 32.5

- Creando clases: Capítulo 6

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

12.1 Números en General


Aquí hay varias pautas para hacer que el uso de números sea menos propenso a errores:

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,

consulte la Sección 12.7,


admite constantes con nombre, utilícelas en su lugar. Si no puede usar constantes con
“Constantes con nombre”, más adelante nombre, use variables globales cuando sea factible hacerlo.
en este capítulo.

Evitar los números mágicos produce tres ventajas:

- 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.

- Su código es más legible. Claro, en la expresión


para i = 0 a 99 hacer...

puedes adivinar eso99se refiere al número máximo de entradas. Pero la expresión

para i = 0 a MAX_ENTRIES-1 hacer...

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.

Use 0s y 1s codificados si es necesarioLos valores0y1se utilizan para incrementar,


disminuir e iniciar bucles en el primer elemento de una matriz. los0en

para i = 0 a CONSTANTE hacer...

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 en Microsoft Visual Basic se podría decir

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

"Evite las comparaciones de igualdad"

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.

Comprobar el desbordamiento de enterosAl hacer una multiplicación o suma de enteros,


debe tener en cuenta el entero más grande posible. El entero sin signo más grande posible
suele ser 232-1 y a veces es 2dieciséis-1, o 65.535. El problema surge cuando multiplicas dos
números que producen un número mayor que el entero máximo. Para
Traducido del inglés al español - www.onlinedoctranslator.com

294 Capítulo 12: Tipos de datos fundamentales

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.

Tabla 12-1 Rangos para diferentes tipos de enteros


Tipo entero Rango
Firmado de 8 bits - 128 a 127 0 a
8 bits sin firmar 255
Firmado de 16 bits - 32.768 a 32.767 0 a
16 bits sin firmar 65.535
Firmado de 32 bits - 2.147.483.648 a 2.147.483.647 0 a
32 bits sin firmar 4.294.967.295
Firmado de 64 bits - 9.223.372.036.854.775.808 a través
9.223.372.036.854.775.807
64 bits sin firmar 0 a 18,446,744,073,709,551,615

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.

Compruebe si hay desbordamiento en los resultados intermediosEl número al final de la ecuación


no es el único número del que debe preocuparse. Supongamos que tiene el siguiente código:

Ejemplo Java de desbordamiento de resultados intermedios


int termA = 1000000; int
termB = 1000000;
int producto = termA * termB / 1000000;
System.out.println( "( " + termA + " * " + termB + " ) / 1000000 = " + producto );

Si piensas que elProductola asignación es la misma que(1,00,000*1,000,000) / 1,000,000, puede


esperar obtener la respuesta1,000,000. Pero el código tiene que calcular el resultado intermedio de
1,000,000*1,000,000antes de que pueda dividirse por el final1,000,000, y eso significa que necesita
un número tan grande como1,000,000,000,000. ¿Adivina qué? Aquí está el resultado:

( 1000000 * 1000000 ) / 1000000 = -727


12.3 Números de punto flotante 295

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

desbordamiento de enteros, cambiando a un tipo entero largo o de punto flotante.

12.3 Números de punto flotante


La consideración principal al usar números de coma flotante es que muchos números decimales
fraccionarios no se pueden representar con precisión usando los 1 y 0 disponibles en una computadora
digital. Los decimales sin terminación como 1/3 o 1/7 generalmente se pueden representar con solo 7 o 15
PUNTO CLAVE
dígitos de precisión. En mi versión de Microsoft Visual Basic, una representación de punto flotante de 32 bits
de 1/3 es igual a 0,33333330. Tiene una precisión de 7 dígitos. Esto es lo suficientemente preciso para la
mayoría de los propósitos, pero lo suficientemente inexacto como para engañarlo a veces.

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.

Ejemplo de Java de una mala comparación de números de coma flotante


La variablenominales un real doble nominal = 1,0; doble
de 64 bits. suma = 0.0;

para (int i = 0; i < 10; i++) {


sumaes 10*0.1. Debería ser suma += 0.1;
1.0. }

Aquí está la mala comparación. si ( nominal == suma ) {


System.out.println("Los números son iguales");
}
más {
System.out.println("Los números son diferentes");
}
296 Capítulo 12: Tipos de datos fundamentales

Como probablemente puedas adivinar, la salida de este programa es

Los números son diferentes.

Los valores línea por línea desumaen elporel bucle se ve así:

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”.
}
}

Si el código en el ejemplo de "comparación incorrecta de números de coma flotante" se


convirtió para que esta rutina pudiera usarse para comparaciones, la nueva comparación se
vería así:

si ( Igual a ( Nominal, Suma ) ) ...

La salida del programa cuando usa esta prueba es

Los números son los mismos.

Dependiendo de las demandas de su aplicación, puede ser inapropiado usar un valor


codificado paraACEPTABLE_DELTA. Es posible que necesite calcular ACEPTABLE_DELTA
en función del tamaño de los dos números que se comparan.
12.4 Caracteres y cadenas 297

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.

Verifique el soporte de idioma y biblioteca para tipos de datos específicosAlgunos lenguajes,


incluido Visual Basic, tienen tipos de datos comoDivisaque admiten específicamente datos sensibles
a errores de redondeo. Si su idioma tiene un tipo de datos incorporado que proporciona dicha
funcionalidad, ¡utilícelo!

12.4 Caracteres y cadenas


Esta sección proporciona algunos consejos para el uso de cadenas. El primero se aplica a cadenas en todos los

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

los de los números mágicos


aparecen a lo largo de un programa. Si programa en un lenguaje que admite el uso de constantes
discutidos en la Sección 12.1, con nombre, utilícelas en su lugar. De lo contrario, utilice variables globales. Existen varias razones
“Números en general”.
para evitar cadenas literales:

- 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.

Ejemplos en C++ de comparaciones usando cadenas


¡Malo! si (entrada_char == 0x1B)... si (entrada_char
== ESCAPE)...
¡Mejor!

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.

Decidir sobre una estrategia de internacionalización/localización al principio de la vida útil de un


programaLas cuestiones relacionadas con la internacionalización y la localización son cuestiones
importantes. Las consideraciones clave son decidir si almacenar todas las cadenas en un recurso externo y
si crear compilaciones separadas para cada idioma o determinar el idioma específico en tiempo de
ejecución.

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

Si necesita admitir varios idiomas, use UnicodeUnicode proporciona un soporte más


completo para juegos de caracteres internacionales que ISO 8859 u otros estándares.

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

StringPtr = "Alguna cadena de texto";

En este caso,"alguna cadena de texto"es un puntero a una cadena de texto literal y la


asignación simplemente establece el punteroCadenaPtrpara apuntar a la cadena de texto. La
asignación no copia los contenidos aCadenaPtr.

- 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

C Ejemplo de buenas declaraciones de cadena


/* Declara que la cadena tiene una longitud de "constante+1".
En cualquier otro lugar del programa, se usa "constante" en lugar de
"constante+1". */
Se declara que la cadena tiene una char nombre[ NOMBRE_LONGITUD + 1 ] = { 0 }; /* cadena de longitud NOMBRE_LONGITUD */
longitudNOMBRE_LONGITUD +1.

...
/* 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í… ...

/* Ejemplo 2: Copiar otra cadena en la primera cadena usando


la constante como la longitud máxima que se puede copiar. */
…y aquí. strncpy( nombre, otro_nombre, LONGITUD_NOMBRE );

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,

“Pautas para inicializar variables”.


larga crea que es la cadena, C no encuentra el final de la cadena hasta que encuentra un byte 0. Si
olvida poner un valor nulo al final de la cadena, es posible que sus operaciones de cadena no actúen
de la forma esperada.

Puede evitar cadenas interminables de dos maneras. Primero, inicialice las matrices de caracteres para0
cuando los declaras:

C Ejemplo de una buena declaración de una matriz de caracteres


char EventName[ MAX_NAME_LENGTH + 1 ] = { 0 };

En segundo lugar, cuando asigne cadenas dinámicamente, inicialícelas para0mediante el


usollamar() en vez demalloc().llamar()asigna memoria y la inicializa para0.malloc()asigna
memoria sin inicializarla, por lo que se arriesga cuando usa la memoria asignada por
malloc().
12.5 Variables booleanas 301

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.

Usarstrncpy()en vez destrcpy()para evitar cadenas interminablesLas rutinas de cadenas en C vienen en


versiones seguras y versiones peligrosas. Las rutinas más peligrosas comostrcpy() ystrcmp()continúe hasta
que se encuentren con un terminador nulo. Sus compañeros más seguros, strncpy()ystrncmp(), tome un
parámetro para la longitud máxima de modo que incluso si las cadenas continúan para siempre, sus
llamadas a funciones no lo harán.

12.5 Variables booleanas


Es difícil hacer un mal uso de las variables lógicas o booleanas, y usarlas cuidadosamente hace que su
programa sea más limpio.

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,

consulte el Capítulo 32, “Código de


prueba sea inconfundible. Por ejemplo, en el siguiente fragmento, no está claro si el propósito de lasi
autodocumentación”. test es para comprobar si se ha completado, si hay una condición de error o si hay alguna otra cosa:

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:

Ejemplo Java de prueba booleana en la que el propósito es claro


terminado = ( ( elementIndex < 0 ) || ( MAX_ELEMENTS < elementIndex ) ); entradarepetida =
(índiceelemento == últimoíndiceelemento);
if ( terminado || entrada repetida ) {
...
}

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

Aquí hay otro ejemplo de una prueba complicada:

Ejemplo de Visual Basic de una prueba complicada


Si (( document.AtEndOfStream() ) Y ( No inputError ) ) Y _
( ( MIN_LINES <= lineCount ) Y ( lineCount <= MAX_LINES ) ) Y _ ( Not ErrorProcessing() )
CODIFICACIÓN Entonces
HORROR
' hacer una cosa u otra . . .

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:

Ejemplo de Visual Basic de una prueba simplificada


allDataRead = ( document.AtEndOfStream() ) And ( Not inputError ) legalLineCount = ( MIN_LINES <=
lineCount ) And ( lineCount <= MAX_LINES ) If ( allDataRead ) And ( legalLineCount ) And ( Not
Aquí está la prueba simplificada. ErrorProcessing() ) Entonces
' hacer una cosa u otra . . .

Terminara si

Esta segunda versión es más sencilla. Supongo que leerá la expresión booleana en elsi
prueba sin ninguna dificultad.

Cree su propio tipo booleano, si es necesarioAlgunos lenguajes, como C++, Java y


Visual Basic, tienen un tipo booleano predefinido. Otros, como C, no. En lenguajes como
C, puede definir su propio tipo booleano. En C, lo harías de esta manera:

C Ejemplo de definición deBOOLEANOEscriba usando un simpledefinición de tipo


typedef int BOOLEANO;

O podría hacerlo de esta manera, lo que brinda el beneficio adicional de definirverdaderoy


falsoal mismo tiempo:

C Ejemplo de definición debooleanoEscriba usando una enumeración


enumeración booleana {

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.

12.6 Tipos enumerados


Un tipo enumerado es un tipo de datos que permite describir en inglés a cada miembro de una
clase de objetos. Los tipos enumerados están disponibles en C++ y Visual Basic y generalmente
se usan cuando conoce todos los valores posibles de una variable y quiere expresarlos en
palabras. Estos son algunos ejemplos de tipos enumerados en Visual Basic:

Ejemplos de Visual Basic de tipos enumerados


Color de enumeración pública

Color rojo
Color verde
Color azul
Enumeración final

País de enumeración pública


País_China
País_Inglaterra
País_Francia
País_Alemania
País_India
País_Japón
País_Estados Unidos

Enumeración final

Salida de enumeración pública


Pantalla_de_salida
Salida_Impresora
Archivo de salida
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

puedes escribir expresiones más legibles como

si se elige Color = Color_Rojo

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.

Comprobar valores no válidosCuando prueba un tipo enumerado en unsiocasoinstrucción, compruebe si


hay valores no válidos. Utilizar elmáscláusula en uncasodeclaración para atrapar valores no válidos:
12.6 Tipos enumerados 305

Buen ejemplo de Visual Basic de verificación de valores no válidos en un tipo enumerado


Seleccione Pantalla de la cajaColor
Caso Color rojo
...
Color de la caja_Azul
...
Color de la caja_Verde
...
Aquí está la prueba para el caso más
valor no válido. DisplayInternalError( False, "Error interno 752: color no válido". ) End Select

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 configuración de Visual BasicPrimeroyUltimoValores en un tipo enumerado


País de enumeración pública
País_primero = 0
País_China = 0
País_Inglaterra = 1
País_Francia = 2
País_Alemania = 3
País_India = 4
País_Japón = 5
País_Estados Unidos = 6
País_Último = 6
Enumeración final

Ahora elPaís_primeroyPaís_ÚltimoLos valores se pueden utilizar como límites de bucle:

Buen ejemplo de Visual Basic de recorrer elementos en una enumeración


' calcula las conversiones de moneda de la moneda de EE. UU. a la moneda de destino Dim
usaCurrencyConversionRate( Country_Last ) As Single
Dim iCountry como país
Para iCountry = Country_First To Country_Last
usaCurrencyConversionRate( iCountry ) = ConversionRate( Country_Usa, iCountry ) Siguiente

Reserve la primera entrada en el tipo enumerado como no válidaCuando declara un tipo


enumerado, reserve el primer valor como un valor no válido. Muchos compiladores asignan el
primer elemento de un tipo enumerado al valor0. Declarar el elemento que está asignado a0
ser inválido ayuda a detectar variables que no se inicializaron correctamente porque es más
probable que sean0que cualquier otro valor inválido.
306 Capítulo 12: Tipos de datos fundamentales

Así es como elPaísdeclaración se vería con ese enfoque:

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

Definir con precisión cómoPrimeroyUltimoelementos se van a utilizar en el estándar de codificación


del proyecto, y utilícelos de forma coherenteUsandoInvalidFirst,Primero, yUltimoLos elementos de las
enumeraciones pueden hacer que las declaraciones de matriz y los bucles sean más legibles. Pero tiene el
potencial de crear confusión acerca de si las entradas válidas en la enumeración comienzan en0o1y si el
primer y el último elemento de la enumeración son válidos. Si se utiliza esta técnica, el estándar de
codificación del proyecto debería exigir queInvalidFirst, Primero, yUltimoelementos se utilicen de forma
coherente en todas las enumeraciones para reducir los errores.

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++:

Ejemplo de C++ de asignación explícita de valores a una enumeración


color de enumeración {

Color_InvalidFirst = 0,
Color_primero = 1,
Color_Rojo = 1,
Color verde = 2,
Color azul = 4,
De color negro = 8,
Color_Último = 8
};

En este ejemplo, si declaró un índice de bucle de tipoColore intentó recorrer


Colors, recorrería los valores no válidos de 3, 5, 6 y 7, así como los valores
válidos de 1, 2, 4 y 8.
12.7 Constantes con nombre 307

Si su idioma no tiene tipos enumerados


Si su idioma no tiene tipos enumerados, puede simularlos con variables o
clases globales. Por ejemplo, podría usar estas declaraciones en Java:

Referencia cruzadaEn el momento Ejemplo de Java de simulación de tipos enumerados


en que escribo esto, Java no es
// configura la clase de tipo enumerada
compatible con enumerados
Country Country {
tipos Para cuando leas esto, país privado () {}
probablemente lo hará. Este es país final estático público China = nuevo país (); país final estático
un buen ejemplo de la "ola de público Inglaterra = nuevo país (); país final estático público Francia =
tecnología" que se analiza en nuevo país (); país final estático público Alemania = nuevo país (); public
la Sección 4.3, "Su ubicación en static final Country India = new Country(); país final estático público
la ola de tecnología". Japón = nuevo país ();

// configurar salida clase de tipo enumerado


Salida {
Salida privada () {}
Pantalla de salida final estática pública = nueva salida (); Impresora
de salida final estática pública = nueva salida (); Archivo de salida final
estático público = nueva salida ();
}

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.

12.7 Constantes con nombre


Una constante con nombre es como una variable, excepto que no puede cambiar el valor de la constante
una vez que la ha asignado. Las constantes con nombre le permiten hacer referencia a cantidades fijas,
como el número máximo de empleados, mediante un nombre en lugar de un número:
MAXIMO_EMPLEADOSmás bien que1000, por ejemplo.

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

aquí. Escribe NÚMERO_TELÉFONO

areaCode( AREA_CODE_LENGTH ) Como cadena


Se usa aquí. localNumber( LOCAL_NUMBER_LENGTH ) Como tipo de final
de cadena
...

' 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?

Ejemplo de Visual Basic de código poco claro


Para i = 1 a 12
ganancia( i ) = ingreso( i ) – gasto( i ) Siguiente
12.7 Constantes con nombre 309

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:

Ejemplo de Visual Basic de código más claro


Para i = 1 a NUM_MONTHS_IN_YEAR
ganancia( i ) = ingreso( i ) – gasto( i ) Siguiente

Esto es mejor, pero, para completar el ejemplo, el índice del bucle también debería llamarse
algo más informativo:

Ejemplo de Visual Basic de código aún más claro


Por mes = 1 a NUM_MONTHS_IN_YEAR
beneficio (mes) = ingresos (mes) – gastos (mes) Siguiente

Este ejemplo parece bastante bueno, pero podemos llevarlo un paso más allá usando un tipo
enumerado:

Ejemplo de Visual Basic de código muy claro


Por mes = mes_enero a mes_diciembre
beneficio (mes) = ingresos (mes) – gastos (mes) Siguiente

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

idioma no tiene tipos


sugerido en el ejemplo anterior de Java en el que se simularon los tipos enumerados, puede obtener
enumerados" en la sección muchas de las ventajas de las constantes con nombre. Se aplican las reglas típicas de alcance:
anterior, Sección 12.6.
prefiera el alcance local, el alcance de clase y el alcance global en ese orden.

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

a tu puerta Si es necesario cambiar el valor de la constante nombrada, lo cambiará y pensará


que ha realizado todos los cambios necesarios. Pasará por alto los literales codificados, su
programa desarrollará defectos misteriosos y solucionarlos será mucho más difícil que levantar
el teléfono y pedir ayuda a gritos.

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

los bucles, consulte el Capítulo 16, “Control de


Pregúntese si el código accede correctamente al primer elemento de la matriz o accede por error al
bucles”. elemento antes o después del primer elemento. ¿Qué pasa con el último elemento? ¿El código
cometerá un error de uno en uno? Finalmente, pregúntese si el código accede correctamente a los
elementos intermedios de la matriz.

Si una matriz es multidimensional, asegúrese de que sus subíndices se utilicen en el orden


correcto es fácil de decirMatriz[ i ][ j ]cuando quieres decirMatriz[ j ][ i ], así que tómese el tiempo
para verificar que los índices estén en el orden correcto. Considere usar nombres más significativos
queiyjen casos en los que sus roles no están claros de inmediato.
12.9 Creación de sus propios tipos (Aliasing de tipos) 311

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.

En C, utilice elARRAY_LENGTH()macro para trabajar con arreglosPuede incorporar


flexibilidad adicional a su trabajo con arreglos definiendo unARRAY_LENGTH()macro
que se ve así:

C Ejemplo de definición de unARRAY_LENGTH()Macro


# define ARRAY_LENGTH( x ) (tamaño(x)/tamaño(x[0]))

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:

C Ejemplo de uso de laARRAY_LENGTH()Macro para operaciones de matriz


Coeficientes de consistencia[] =
{ 0,0, 0,0, 0,58, 0,90, 1,12, 1,24, 1,32,
1,41, 1,45, 1,49,
1,51, 1,48, 1,56, 1,57, 1,59};
...
Aquí es donde se usa la for ( ratioIdx = 0; ratioIdx < ARRAY_LENGTH(CoherencyRatios ); ratioIdx++ );
macro. ...

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.

12.9 Creación de sus propios tipos (Aliasing de tipos)


Los tipos de datos definidos por el programador son una de las capacidades más poderosas
que un lenguaje puede brindarle para aclarar su comprensión de un programa. Protegen su
programa contra cambios imprevistos y lo hacen más fácil de leer, todo sin necesidad de
PUNTO CLAVE
diseñar, construir o probar nuevas clases. Si está utilizando C, C++ u otro lenguaje que permita
tipos definidos por el usuario, ¡aprovéchelos!

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

más información, consulte el Capítulo


necesitarse números de punto flotante de precisión doble, pero preferiría escribir un programa con
6, “Funcionamiento números de punto flotante de precisión simple hasta que esté absolutamente seguro. Puede crear
Clases. un nuevo tipo específicamente para coordenadas utilizando undefinición de tipodeclaración en
312 Capítulo 12: Tipos de datos fundamentales

C o C++ o el equivalente en otro idioma. Así es como configuraría la definición de


tipo en C++:

Ejemplo en C++ de creación de un tipo


typedef float Coordenada; // para variables de coordenadas

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:

Ejemplo de C++ del uso del tipo que ha creado


Rutina1( ... ) {
Coordenada latitud; // latitud en grados // longitud
Coordenada de longitud; en grados
Coordenada de elevación; // elevación en metros desde el centro de la tierra
...
}
...

Rutina2( ... ) {
Coordenada x; // coordenada x en metros //
Coordenada y; coordenada y en metros //
Coordenada z; coordenada z en metros
...
}

En este código, las variableslatitud,longitud,elevación,X,y, yzse declaran todos del


tipoCoordinar.

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:

Ejemplo de C++ de definición de tipo modificada


El originalflotarha typedef doble Coordenada; // para variables de coordenadas
cambiado adoble.

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:

Ejemplo Pascal de creación de un tipo para nombres de empleados


Escribe

nombreEmpleado = array[ 1..30 ] of char;


12.9 Creación de sus propios tipos (Aliasing de tipos) 313

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:

Pascal Ejemplo de Mejor Creación de Tipos


constante

Aquí está la declaración de la NOMBRE_LONGITUD = 30;


constante nombrada. ...
Escribe

Aquí es donde se usa la nombre_empleado = matriz[ 1..LONGITUD_NOMBRE ] of char;


constante nombrada.

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.

El ejemplo de las coordenadas en C++ está a medio camino de la ocultación de información. Si


siempre usasCoordinarmás bien queflotarodoble, efectivamente oculta el tipo de datos. En C+
+, se trata de toda la información que oculta el lenguaje por ti. Por lo demás, usted o los
usuarios posteriores de su código deben tener la disciplina de no buscar la definición de
Coordinar. C ++ le brinda una capacidad de ocultación de información figurativa, en lugar de
literal.

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:

Ejemplo de Ada de ocultar detalles de un tipo dentro de un paquete


La transformación del paquete es
Esta declaración declara el tipo Coordinate es privado; . . .
Coordinarcomo privado al
paquete.

Así es cómoCoordinarbusca en otro paquete, uno que lo use:

Ejemplo de Ada del uso de un tipo de otro paquete


con Transformación;
...
procedimiento Rutina1(...) ...
latitud: Coordinar;
longitud: Coordinar;
empezar

- - declaraciones usando latitud y longitud


...
finalizar Rutina1;

V413HAV
314 Capítulo 12: Tipos de datos fundamentales

Note que elCoordinartipo se declara comoprivadoen la especificación del paquete. Eso


significa que la única parte del programa que conoce la definición delCoordinar type es la
parte privada delTransformaciónpaquete. En un entorno de desarrollo con un grupo de
programadores, podría distribuir solo la especificación del paquete, lo que dificultaría que
un programador que trabaja en otro paquete busque el tipo subyacente deCoordinar. La
información estaría literalmente oculta. Lenguajes como C++ que requieren que
distribuyas la definición deCoordinaren los archivos de encabezado socavan la ocultación
de información verdadera.

Estos ejemplos han ilustrado varias razones para crear sus propios tipos:

- Para facilitar las modificacionesEs poco trabajo crear un nuevo tipo y le da


mucha flexibilidad.

- 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.

- Para compensar las debilidades del lenguaje.Si su idioma no tiene el tipo


predefinido que desea, puede crearlo usted mismo. Por ejemplo, C no tiene un tipo
booleano o lógico. Esta deficiencia es fácil de compensar creando el tipo usted
mismo:
typedef int Booleano;

¿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

temperaturaActual: Rango INTEGER 0..212;

contiene información semántica importante que una declaración como

temperatura interna;

no es. Yendo un paso más allá, una declaración de tipo como

tipo La temperatura está en el rango


0..212; . . .
temperaturaActual: Temperatura;
12.9 Creación de sus propios tipos (Aliasing de tipos) 315

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.

Pautas para crear sus propios tipos


Referencia cruzadaEn cada caso, Tenga en cuenta estas pautas cuando cree sus propios tipos "definidos por el usuario":
considere si crear una clase podría
funcionar mejor que un tipo de Crear tipos con nombres orientados funcionalmenteEvite nombres de tipo que hagan referencia al tipo
datos simple. Para más detalles,
de datos informáticos subyacentes al tipo. Utilice nombres de tipo que hagan referencia a las partes del
consulte el Capítulo 6, “Clases
trabajadoras”. problema del mundo real que representa el nuevo tipo. En los ejemplos anteriores, las definiciones crearon
tipos bien nombrados para coordenadas y nombres: entidades del mundo real. De manera similar, podría
crear tipos para moneda, códigos de pago, edades, etc., aspectos de problemas del mundo real.

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.

No redefina un tipo predefinidoCambiar la definición de un tipo estándar puede crear


confusión. Por ejemplo, si su idioma tiene un tipo predefinidoEntero, no crees tu propio
tipo llamadoEntero. Los lectores de su código pueden olvidar que ha redefinido el tipo y
suponer que elEnteroven es elEnteroestán acostumbrados a ver.

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”.

cc2e.com/1206 LISTA DE VERIFICACIÓN: Datos fundamentales

Referencia cruzadaPara Números en General


lista de verificación que se aplica a
- ¿El código evita los números mágicos?
problemas de datos generales en lugar de

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

257 en el Capítulo 10, "Problemas - ¿Son obvias las conversiones de tipos?


generales en el uso de variables". Para

obtener una lista de verificación de


- Si se utilizan variables con dos tipos diferentes en la misma expresión,
consideraciones al nombrar variedades, ¿se evaluará la expresión como se pretende?
consulte la lista de verificación en la página

288 en el Capítulo 11, "El poder de los


- ¿El código evita las comparaciones de tipo mixto?

¿El programa se compila sin advertencias?


nombres de variables".
-

enteros
- ¿Las expresiones que usan la división de enteros funcionan de la forma en que deben hacerlo?

- ¿Las expresiones enteras evitan los problemas de desbordamiento de enteros?

Números de punto flotante


- ¿Evita el código sumas y restas de números con magnitudes muy
diferentes?
- ¿El código previene sistemáticamente los errores de redondeo?

- ¿Evita el código comparar números de punto flotante para la igualdad?

Caracteres y cadenas
- ¿El código evita cadenas y caracteres mágicos?

- ¿Las referencias a cadenas están libres de errores de uno en uno?


12.9 Creación de sus propios tipos (Aliasing de tipos) 317

- ¿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?

- ¿El código C usa matrices de caracteres en lugar de punteros, cuando corresponde?

- ¿El código C inicializa cadenas paraNULOs para evitar cadenas interminables?

- ¿El código C usastrncpy()más bien questrcpy()? Ystrncat()y


strncmp()?

Variables booleanas
- ¿El programa utiliza variables booleanas adicionales para documentar las pruebas
condicionales?

- ¿Utiliza el programa variables booleanas adicionales para simplificar 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?

- ¿Las pruebas que usan tipos enumerados prueban valores no válidos?

- ¿La primera entrada en un tipo enumerado está reservada para "no válido"?

Constantes con nombre

- ¿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

- ¿Están todos los índices de la matriz dentro de los límites de la matriz?

- ¿Las referencias de matriz están libres de errores de uno en uno?

- ¿Están todos los subíndices en arreglos multidimensionales en el orden correcto?

- 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?

- ¿Ha evitado redefinir tipos predefinidos?


- ¿Ha considerado crear una nueva clase en lugar de simplemente redefinir un
tipo?

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.

- Cuando crea un tipo simple usandodefinición de tipoo su equivalente, considere si


debería crear una nueva clase en su lugar.
Capítulo 13

Tipos de datos inusuales


cc2e.com/1378 Contenido

- 13.1 Estructuras: página 319

- 13.2 Punteros: página 323

- 13.3 Datos globales: página 335

Temas relacionados

- Tipos de datos fundamentales: Capítulo 12

- Programación defensiva: Capítulo 8

- Estructuras de control inusuales: Capítulo 17

- Complejidad en el desarrollo de software: Sección 5.2

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:

Ejemplo de Visual Basic de variables engañosas y no estructuradas


nombre = nombre de entrada

Dirección = dirección de entrada


teléfono = teléfono de entrada

título = título de entrada


Departamento = departamento de entrada
bono = bono de entrada

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:

Ejemplo de Visual Basic de variables estructuradas más informativas


empleado.nombre = nombre de entrada

dirección.del.empleado = dirección de entrada


empleado.teléfono = teléfono de entrada

supervisor.title = inputTitle supervisor.departamento


= inputDepartment supervisor.bonus = inputBonus

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.

Use estructuras para simplificar operaciones en bloques de datosPuede combinar elementos


relacionados en una estructura y realizar operaciones en la estructura. Es más fácil operar sobre la
estructura que realizar la misma operación sobre cada uno de los elementos. También es más
confiable y requiere menos líneas de código.

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

Ejemplo de Visual Basic de la copia torpe de un grupo de elementos de datos


nuevoNombre = viejo nombre

nueva direccion = dirección antigua


teléfono nuevo = telefono viejo
nuevoSsn = viejoSsn
nuevoGénero = viejoGénero
nuevoSalario = antiguosalario

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á:

Ejemplo de Visual Basic de intercambio de dos grupos de datos de la manera difícil


' intercambiar datos de empleados nuevos y
antiguos previousOldName = viejo nombre
CODIFICACIÓN dirección anterior anterior = dirección antigua
HORROR
anteriorTeléfono antiguo = telefono viejo
anteriorAntiguoSsn = viejoSsn
anteriorAntiguoGénero = viejoGénero
anteriorAntiguoSalario = antiguosalario

nombreAntiguo = nuevo nombre

antigua dirección = nueva dirección


telefono viejo = teléfono nuevo

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:

Ejemplo de Visual Basic de declaración de estructuras


Estructura Empleado
nombre como Cuerda
Dirección Como cuerda
teléfono Como
Cuerda
como Cuerda
género Como
Cuerda
salario Como
largo
322 Capítulo 13: Tipos de datos inusuales

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

Si desea agregar un campo comonúmRetenciones, simplemente lo agregas a laEstructura


declaración. No es necesario modificar las tres declaraciones anteriores ni ninguna declaración
similar a lo largo del programa. C++ y otros lenguajes tienen capacidades similares.

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

"Mantener el acoplamiento suelto" en la


lugar de pasar cada uno de los elementos necesarios individualmente, puede agrupar elementos
Sección 5.3. relacionados en una estructura y pasar toda la enchilada como una estructura de grupo. Aquí hay un
ejemplo de la manera difícil de pasar un grupo de parámetros relacionados:

Ejemplo de Visual Basic de una llamada de rutina torpe sin estructura


HardWayRoutine (nombre, dirección, teléfono, ssn, género, salario)

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)

Si quieres agregarnúmRetencionesal primer tipo de llamada, debe leer su código y


cambiar cada llamada aRutina HardWay(). Si agregas unnúmRetencioneselemento a
Empleado, no es necesario cambiar los parámetros paraEasyWayRoutine()en absoluto.

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

acoplamiento suelto" en la Sección 5.3.


datos más de lo que es lógicamente necesario. Además, los programadores cuidadosos evitan pasar
una estructura como parámetro cuando solo se necesitan uno o dos campos de la estructura; en su
lugar, pasan los campos específicos necesarios. Este es un aspecto de la ocultación de información:
parte de la información está ocultaenrutinas, y algunas están ocultasderutinas La información se
transmite según la necesidad de saber.
13.2 Punteros 323

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.

Paradigma para comprender los punteros


Conceptualmente, cada puntero consta de dos partes: una ubicación en la memoria y un
conocimiento de cómo interpretar los contenidos de esa ubicación.

Ubicación en la memoria

La ubicación en la memoria es una dirección, a menudo expresada en notación hexadecimal.


Una dirección en un procesador de 32 bits sería un valor de 32 bits, como0x0001EA40. El
puntero en sí contiene solo esta dirección. Para usar los datos a los que apunta el puntero,
debe ir a esa dirección e interpretar el contenido de la memoria en esa ubicación. Si mirara la
memoria en esa ubicación, sería solo una colección de bits. Tiene que ser interpretado para
que tenga sentido.
Traducido del inglés al español - www.onlinedoctranslator.com

324 Capítulo 13: Tipos de datos inusuales

Conocimiento de cómo interpretar los contenidos

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.

Al pensar en punteros, es útil recordar que la memoria no tiene ninguna interpretación


inherente asociada con ella. Es solo mediante el uso de un tipo específico de puntero que los
bits en una ubicación particular se interpretan como datos significativos.

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

Visto como: entero de 2 bytes


Interpretado como: 24842

0A 61 62 63 64 66 67 68 69 6A
sesenta y cinco

Visto como: punto flotante de 4 bytes


Interpretado como: 4.17595656202980E+0021

0A 61 62 63 64 66 67 68 69 6A
sesenta y cinco

Visto como: entero de 4 bytes


Interpretado como: 1667391754

0A 61 62 63 64 66 67 68 69 6A
sesenta y cinco

Visto como: carbonizarse

Interpretado como: carácter de avance de línea (ASCII hexadecimal 0A o decimal 10)

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.

Consejos generales sobre punteros

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:

Ejemplo de C++ de inicialización de puntero incorrecto


Empleado *empleadoPtr;
// mucho código
CODIFICACIÓN ...
HORROR
empleadoPtr = nuevo empleado;
326 Capítulo 13: Tipos de datos inusuales

Incluso si este código funciona correctamente inicialmente, es propenso a errores si se modifica


porque existe la posibilidad de que alguien intente usarempleadoPtrentre el punto donde se declara
el puntero y el momento en que se inicializa. Aquí hay un enfoque más seguro:

Ejemplo de C++ de buena inicialización de puntero


// mucho código
...
Empleado *employeePtr = nuevo Empleado;

Eliminar punteros en el mismo nivel de ámbito en el que se asignaronMantenga la asignación y


desasignación de punteros simétricos. Si usa un puntero dentro de un solo ámbito, llamenuevo
asignar yEliminarpara desasignar el puntero dentro del mismo ámbito. Si asigna un puntero dentro
de una rutina, desasignarlo dentro de una rutina hermana. Si asigna un puntero dentro del
constructor de un objeto, desasignarlo dentro del destructor del objeto. Una rutina que asigna
memoria y luego espera que su código de cliente desasigne la memoria manualmente crea una
inconsistencia que está lista para el error.

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:

1.Primero,nuevo104 bytes, 4 bytes más de lo solicitado.

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

comienza después de eso.

Coloque el puntero aquí.

etiqueta

3.Cuando llegue el momento de eliminar el puntero, verifique la etiqueta.

Revisa esta 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.

5.Finalmente, elimine el puntero.

Libera los 104 bytes completos

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.

Agregar redundancias explícitasUna alternativa al uso de un campo de etiqueta es usar ciertos


campos dos veces. Si los datos en los campos redundantes no coinciden, sabrá que la memoria está
dañada. Esto puede resultar en una gran sobrecarga si manipula los punteros directamente. Sin
embargo, si aísla las operaciones de puntero en las rutinas, agrega código duplicado solo en algunos
lugares.

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:

Ejemplo de C++ de código de inserción de nodo tradicional


vacío Insertar enlace (
Nodo * nodoactual,
Nodo * insertarNodo
){
// inserte "insertNode" después de "currentNode"
insertNode->next = currentNode->next; insertNode-
>anterior = currentNode;
if (NodoActual->siguiente!= NULL) {
Esta línea es nodoactual->siguiente->anterior = insertarNodo;
innecesariamente difícil. }
nodoactual->siguiente = insertarNodo;
}

Este es un código tradicional para insertar un nodo en una lista enlazada y es


innecesariamente difícil de entender. La inserción de un nuevo nodo implica tres objetos:
el nodo actual, el nodo que actualmente sigue al nodo actual y el nodo que se insertará
entre ellos. El fragmento de código reconoce explícitamente solo dos objetos:
insertarNodoynodoactual. Te obliga a descubrir y recordar queactualNodo->siguiente
también está involucrado. Si intenta diagramar lo que sucede sin que el nodo siga
originalmentenodoactual, obtendrías algo como esto:

nodoactual insertarNodo

Un diagrama mejor identificaría los tres objetos. Se vería así:

nodo de inicio nuevoNodoMedio nodo siguiente

Aquí hay un código que hace referencia explícita a los tres objetos involucrados:

Ejemplo de C++ de código de inserción de nodo más legible


vacío Insertar enlace (
Nodo * nodo de inicio,
Nodo * nuevoNodoMedio
){
// insertar "nuevoNodoMedio" entre "NodoInicio" y "NodoSiguiente" Nodo
*NodoSiguiente = NodoInicio->siguiente;
nuevoNodoMedio->siguiente = siguienteNodo;
newMiddleNode->anterior = startNode; if
(siguienteNodo!= NULL) {
siguienteNodo->anterior = nuevoNodoMedio;
}
startNode->next = newMiddleNode;
}
13.2 Punteros 329

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.

Simplificar expresiones de puntero complicadasLas expresiones de puntero complicadas son


difíciles de leer. Si su código contiene expresiones como p->q->r->s.data, piense en la persona que
tiene que leer la expresión. He aquí un ejemplo particularmente atroz:

Ejemplo en C++ de una expresión de puntero que es difícil de entender


for ( rateIndex = 0; rateIndex < numRates; rateIndex++ ) {
netRate[ rateIndex ] = baseRate[ rateIndex ] * tarifas->descuentos->factores->neto;
CODIFICACIÓN }
HORROR

Las expresiones complicadas, como la expresión de puntero en este ejemplo, generan un


código que debe resolverse en lugar de leerse. Si su código contiene una expresión
complicada, asígnela a una variable bien nombrada para aclarar la intención de la operación.
Aquí hay una versión mejorada del ejemplo:

Ejemplo de C++ de simplificación de una expresión de puntero complicada


cantidadDescuento = tarifas->descuentos->factores->neto;
for ( rateIndex = 0; rateIndex < numRates; rateIndex++ ) {
netRate[ rateIndex ] = baseRate[ rateIndex ] * cantidadDescuento;
}

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.

Referencia cruzadaLos diagramas


Vinculación inicial
como el de la Figura 13-2 pueden

convertirse en parte de la startNode->siguiente

documentación externa de su nodo de inicio nodo siguiente


programa. Para obtener detalles nodo siguiente->anterior
sobre las buenas prácticas de

documentación, consulte el Capítulo Vinculación deseada


32, “Código de autodocumentación”.
startNode->siguiente nodo siguiente->anterior

nodo de inicio nodo siguiente

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

Eliminar punteros en listas vinculadas en el orden correctoUn problema común al trabajar


con listas vinculadas asignadas dinámicamente es liberar primero el primer puntero de la lista
y luego no poder acceder al siguiente puntero de la lista. Para evitar este problema, asegúrese
de tener un puntero al siguiente elemento de una lista antes de liberar el actual.

Asignar un paracaídas de reserva de memoriaSi su programa usa memoria dinámica, debe


evitar el problema de quedarse sin memoria repentinamente, dejando a su usuario y los datos
de su usuario perdidos en el espacio RAM. Una forma de darle a su programa un margen de
error es preasignar un paracaídas de memoria. Determine cuánta memoria necesita su
programa para ahorrar trabajo, limpiar y salir correctamente. Asigne esa cantidad de memoria
al comienzo del programa como un paracaídas de reserva y déjelo solo. Cuando se quede sin
memoria, libere el paracaídas de reserva, limpie y apague.

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:

C Ejemplo de forzar un objeto desasignado para que contenga datos basura


puntero->EstablecerContenidoALaBasura();
borrar puntero;

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:

Ejemplo de C++ de establecer un puntero en nulo después de eliminarlo


puntero->EstablecerContenidoALaBasura();
Eliminar puntero;
puntero = NULO;
13.2 Punteros 331

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:

Ejemplo de C++ para afirmar que un puntero no es nulo antes de eliminarlo


ASSERT( puntero != NULL, "Intentando eliminar el puntero nulo." ); puntero-
>EstablecerContenidoALaBasura();
Eliminar puntero;
puntero = NULO;

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:

Ejemplo de C++ para comprobar si se ha asignado un puntero


ASSERT( puntero != NULL, "Intentando eliminar el puntero nulo." ); if
(EsPunteroEnLaLista(puntero)) {
puntero->EstablecerContenidoALaBasura();
RemovePointerFromList( puntero);
Eliminar puntero;
puntero = NULO;
}
más {
ASSERT( FALSE, "Intentando borrar el puntero no asignado." );
}

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.

Puede minimizar la sobrecarga de programación y reducir la posibilidad de errores mediante la creación de


rutinas de cobertura para operaciones de puntero comunes. En C++, podría usar estas dos rutinas:

- 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

- SAFE_DELETEEsta rutina verifica si el puntero que se le pasó está en la lista de punteros


asignados. Si está en la lista, establece la variable a la que apuntó el puntero en valores
basura, elimina el puntero de la lista, llama al operador de eliminación de C++ para
desasignar el puntero y establece el puntero en nulo. Si el puntero no está en la lista,
SAFE_DELETEmuestra un mensaje de diagnóstico y detiene el programa.

Implementado aquí como una macro, elSAFE_DELETEla rutina se ve así:

Ejemplo de C++ de poner un contenedor alrededor del código de eliminación de puntero


# define SAFE_DELETE( puntero ) { \
ASSERT( puntero != NULL, "Intentando eliminar el puntero nulo."); \ if
(EsPunteroEnLaLista(puntero)) {\
puntero->EstablecerContenidoALaBasura();
RemovePointerFromList(puntero); \ borrar
puntero; \
puntero = NULL; \
}\
más { \
ASSERT( FALSE, "Intentando borrar el puntero no asignado." ); \
}\
}

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,

consulte "Plan para eliminar las ayudas


Al centralizar el manejo de la memoria en estas dos rutinas, también puede hacer SEGURO_NUEVOy
para la depuración" en la Sección 8.6.

SAFE_DELETEse comportan de manera diferente en el modo de depuración frente al modo de producción.


por ejemplo, cuandoSAFE_DELETEdetecta un intento de liberar un puntero nulo durante el desarrollo, podría
detener el programa, pero durante la producción podría simplemente registrar un error y continuar con el
procesamiento.

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.

Use punteros para los parámetros de "pasar por referencia" y useconstantereferencias


para parámetros de “paso por valor”C++ por defecto pasa argumentos a las rutinas por valor
en lugar de por referencia. Cuando pasa un objeto a una rutina por valor, C++ crea una copia
del objeto, y cuando el objeto se vuelve a pasar a la rutina de llamada, se crea una copia
nuevamente. Para objetos grandes, esa copia puede consumir tiempo y otros recursos. En
consecuencia, al pasar objetos a una rutina, normalmente querrá evitar copiar el objeto, lo que
significa que querrá pasarlo por referencia en lugar de por valor.

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:

Ejemplo de C++ de pasar parámetros por referencia y por valor


void AlgunaRutina(
const LARGE_OBJECT &nonmodifiableObject,
LARGE_OBJECT *modificableObject
);

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.

La limitación de este enfoque son las dificultades para propagarconstantereferencias Si controla su


propia base de código, es una buena disciplina para usarconstantesiempre que sea posible (Meyers
1998), y debería poder declarar parámetros de paso por valor comoconstantereferencias Para el
código de la biblioteca u otro código que no controla, tendrá problemas al usarconstanteparámetros
de rutina. La posición alternativa sigue siendo usar referencias para parámetros de solo lectura, pero
no declararlos.constante. Con ese enfoque, no se dará cuenta de todos los beneficios de que el
compilador verifique los intentos de modificar los argumentos no modificables de una rutina, pero al
menos obtendrá la distinción visual entreobjeto->miembroyobjeto.miembro.

Usarauto_ptrsSi no has desarrollado el hábito de usarauto_ptr¡Adquiera el hábito! Borrando la


memoria automáticamente cuando elauto_ptrsale fuera de alcance, auto_ptrs evitar muchos de
los problemas de pérdida de memoria asociados con los punteros normales. En Scott MeyersC+
+ más efectivo, el ítem #9 contiene una buena discusión de auto_ptr(Meyers 1996).
334 Capítulo 13: Tipos de datos inusuales

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:

C Ejemplo de conversión de tipo explícito


NodePtr = (NODE_PTR) calloc( 1, sizeof( NODE ) );

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

C Ejemplo de paso de parámetros que no funcionarán


void TryToPassBackAValue( int *parámetro ) {
parámetro = ALGÚN_VALOR;
}

Aquí, el valor asignado aparámetrose transmite porqueparámetrotiene un asterisco


delante:

C Ejemplo de paso de parámetros que funcionará


void TryToPassBackAValue( int *parámetro ) {
* parámetro = ALGÚN_VALOR;
}

Usartamaño de()para determinar el tamaño de una variable en una asignación de memoriaes


más fácil de usartamaño de()que buscar el tamaño en un manual, ytamaño de()funciona para
estructuras que creas tú mismo, que no están en el manual. Debido a que se calcula en tiempo de
compilación, tamaño de()no conlleva una penalización de rendimiento. Es portátil: volver a compilar
en un entorno diferente cambia automáticamente el valor calculado portamaño de(). Y requiere poco
mantenimiento ya que puede cambiar los tipos que haya definido y las asignaciones se ajustarán
automáticamente.

13.3 Datos globales


Referencia cruzadaPara obtener Las variables globales son accesibles en cualquier parte de un programa. El término también se usa a veces de
detalles sobre las diferencias entre
manera descuidada para referirse a variables con un alcance más amplio que las variables locales, como las
datos globales y datos de clase,

consulte "Datos de clase confundidos


variables de clase a las que se puede acceder desde cualquier lugar dentro de una clase. Pero la accesibilidad en
con datos globales" en la Sección 5.3. cualquier lugar dentro de una sola clase no significa por sí misma que una variable sea global.

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

datos de varias rutinas es bastante útil.

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

Problemas comunes con los datos globales


Si usa variables globales de forma indiscriminada o siente que no poder usarlas es restrictivo,
probablemente aún no se haya dado cuenta del valor total de la ocultación de información y la
modularidad. La modularidad, la ocultación de información y el uso asociado de clases bien
diseñadas pueden no ser verdades reveladas, pero contribuyen en gran medida a hacer que
los programas grandes sean comprensibles y fáciles de mantener. Una vez que reciba el
mensaje, querrá escribir rutinas y clases con la menor conexión posible con las variables
globales y el mundo exterior.
336 Capítulo 13: Tipos de datos inusuales

Las personas citan numerosos problemas en el uso de datos globales, pero los problemas se reducen a una

pequeña cantidad de problemas importantes:

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.

Problemas de alias extraños y emocionantes con datos globales“Aliasing” se refiere a


llamar a la misma variable por dos o más nombres diferentes. Esto sucede cuando se pasa una
variable global a una rutina y luego la rutina la utiliza como variable global y como parámetro.
Aquí hay una rutina que usa una variable global:

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

CODIFICACIÓN VarGlobal = VarEntrada + 5


HORROR
MsgBox( "Variable de entrada: " & Str( inputVar ) )
MsgBox( "Variable global: " & Str( globalVar ) ) End Sub

Aquí está el código que llama a la rutina con la variable global como argumento:

Ejemplo de Visual Basic de llamar a la rutina con un argumento, que expone un


problema de alias
WriteGlobal( globalVar )

Ya queentradaVarse inicializa a0yEscribirGlobal()agrega5aentradaVarLlegarglobalvar,


esperaríasglobalvarser 5 mas queentradaVar. Pero aquí está el sorprendente resultado:

El resultado del problema de aliasing en Visual Basic


Aporte Variable: 5
Global Variable: 5

La sutileza aquí es queglobalvaryentradaVarson en realidad la misma variable! Ya que


globalvarse pasa aEscribirGlobal()por la rutina de llamada, se hace referencia o
13.3 Datos globales 337

"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.

Reutilización de código obstaculizada por datos globalesPara usar el código de un


programa en otro programa, debe poder sacarlo del primer programa y conectarlo al
segundo. Idealmente, sería capaz de sacar una sola rutina o clase, conectarla a otro programa
y continuar felizmente su camino.

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

asegurarse de que las dos variables se inicialicen en la secuencia correcta.

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.

Razones para usar datos globales

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

nombre son datos globales, pero no variables globales.

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

Use datos globales solo como último recurso

Antes de recurrir al uso de datos globales, considere algunas alternativas:

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.

Distinguir entre variables globales y de claseAlgunas variables son verdaderamente globales en el


sentido de que se accede a ellas a través de un programa completo. Otras son realmente variables de clase,
que se usan mucho solo dentro de un cierto conjunto de rutinas. Está bien acceder a una variable de clase
de la forma que desee dentro del conjunto de rutinas que la usan mucho. Si las rutinas fuera de la clase
necesitan usarla, proporcione el valor de la variable mediante una rutina de acceso. No acceda a los valores
de clase directamente, como si fueran variables globales, incluso si su lenguaje de programación se lo
permite. Este consejo equivale a decir “¡Modularizar! Modularizar! Modularizar!”

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.

Uso de rutinas de acceso en lugar de datos globales


Cualquier cosa que pueda hacer con datos globales, puede hacerlo mejor con rutinas de acceso. El
uso de rutinas de acceso es una técnica fundamental para implementar tipos de datos abstractos y
lograr la ocultación de información. Incluso si no desea utilizar un tipo de datos abstracto completo,
PUNTO CLAVE
aún puede utilizar las rutinas de acceso para centralizar el control de sus datos y protegerse contra
los cambios.

Ventajas de las Rutinas de Acceso

El uso de rutinas de acceso tiene múltiples ventajas:

- 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

programa para contener el daño


olvidarse fácilmente de verificar el desbordamiento de la pila y cometer un error grave. Si
causado por los errores”. utiliza rutinas de acceso, por ejemplo,PushStack (elemento nuevo)—puedes escribir el cheque
para el desbordamiento de pila en elPushStack()rutina. La comprobación se realizará
automáticamente cada vez que se llame a la rutina, y puedes olvidarte de ella.
340 Capítulo 13: Tipos de datos inusuales

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

información)" en la Sección 5.3.


motivo. Puede cambiar el interior de una rutina de acceso sin cambiar el resto del
programa. Las rutinas de acceso te permiten redecorar el interior de tu casa y dejar el
exterior sin cambios para que tus amigos aún lo reconozcan.

- 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.

Cómo usar las rutinas de acceso

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.

Por ejemplo, si tiene una variable de estado globalg_globalStatusque describe el estado


general de su programa, puede crear dos rutinas de acceso:globalStatus.Get()yestado-
global.Set(), cada uno de los cuales hace lo que parece que hace. Esas rutinas acceden a una
variable oculta dentro de la clase que reemplazag_globalStatus. El resto del programa puede
obtener todos los beneficios de la antigua variable global accediendoglobalStatus.Get() y
globalStatus.Set().

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.

Construya un nivel de abstracción en sus rutinas de accesoCree rutinas de acceso al nivel


del dominio del problema en lugar de al nivel de los detalles de implementación. Ese enfoque
le ofrece una legibilidad mejorada, así como un seguro contra cambios en los detalles de
implementación.

Compare los pares de declaraciones en la tabla 13-1:

Tabla 13-1 Acceso a datos globales directamente y a través de rutinas de acceso

Uso directo de datos globales Uso de datos globales a través de rutinas de acceso

nodo = nodo.siguiente cuenta = NextAccount( cuenta )


nodo = nodo.siguiente empleado = NextEmployee( empleado )
nodo = nodo.siguiente rateLevel = NextRateLevel( rateLevel )
event = eventQueue[ queueFront ] event = HighestPriorityEvent()
event = eventQueue[ queueBack ] evento = evento de prioridad más baja ()

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

crear una clase. Para más detalles,


operaciones de la tabla, ya que requeriría varias líneas de código para encontrar el lugar para
consulte el Capítulo 6, “Clases insertar el evento, ajustar los eventos existentes para dejar espacio para el nuevo evento y ajustar la
trabajadoras”.
parte delantera o trasera. de la cola Quitar un evento de la cola sería igual de complicado. Durante la
codificación, las operaciones complejas se pondrían en rutinas y las demás se dejarían como
manipulaciones directas de datos. Esto crearía un uso feo y no paralelo de la estructura. Ahora
compare los pares de declaraciones en la tabla 13-2:

Tabla 13-2 Usos paralelos y no paralelos de datos complejos


Uso no paralelo de datos complejos Uso paralelo de datos complejos
event = EventQueue[ queueFront ] evento = evento de prioridad más alta()

event = EventQueue[ queueBack ] evento = evento de prioridad más baja()

AddEvent( event ) AddEvent( evento )

eventCount = eventCount - 1 RemoveEvent (evento)

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.

Cómo reducir los riesgos del uso de datos globales


En la mayoría de los casos, los datos globales son realmente datos de clase para una clase que no ha
sido diseñada o implementada muy bien. En algunos casos, los datos realmente necesitan ser
globales, pero los accesos a ellos se pueden encapsular con rutinas de acceso para minimizar los
problemas potenciales. En una pequeña cantidad de instancias restantes, realmente necesita usar
datos globales. En esos casos, puede pensar en seguir las pautas de esta sección como ponerse
inyecciones para poder beber el agua cuando viaje a un país extranjero: son un poco dolorosas, pero
mejoran las probabilidades de mantenerse saludable.

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

"Identificar variables globales" en la


usa variables globales para más de un propósito (por ejemplo, como variables y como sustitutos de
Sección 11.4. constantes con nombre), asegúrese de que su convención de nomenclatura diferencie los tipos de
usos.
Recursos adicionales 343

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 use variables globales para contener resultados intermediosSi necesita calcular


un nuevo valor para una variable global, asigne a la variable global el valor final al final
del cálculo en lugar de utilizarlo para contener el resultado de cálculos intermedios.

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++.

cc2e.com/1392 LISTA DE VERIFICACIÓN: Consideraciones sobre el uso de tipos de datos inusuales

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?

- ¿Las convenciones de nomenclatura de variables diferencian entre datos locales, de clase y


globales?

- ¿Están documentadas todas las variables globales?


344 Capítulo 13: Tipos de datos inusuales

- ¿El código está libre de datos pseudoglobales: objetos gigantescos que contienen
una mezcla de datos que se pasan a cada rutina?

- ¿Se utilizan rutinas de acceso en lugar de datos globales?

- ¿Las rutinas de acceso y los datos están organizados en clases?

- ¿Las rutinas de acceso proporcionan un nivel de abstracción más allá de las implementaciones de
tipos de datos subyacentes?

- ¿Están todas las rutinas de acceso relacionadas en el mismo nivel de abstracción?

Punteros
- ¿Las operaciones de puntero están aisladas en las rutinas?

- ¿Son válidas las referencias de puntero o podría estar colgando el puntero?

- ¿El código verifica la validez de los punteros antes de usarlos?

- ¿Se verifica la validez de la variable a la que hace referencia el puntero antes de


usarla?

- ¿Los punteros se establecen en nulo después de que se liberan?

- ¿El código utiliza todas las variables de puntero necesarias para facilitar la lectura?

- ¿Se liberan los punteros en las listas enlazadas en el orden correcto?

- ¿Asigna el programa un paracaídas de memoria de reserva para que pueda


apagarse correctamente si se queda sin memoria?

- ¿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

más fáciles de mantener.

- 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:

Capítulo 14: Organización del código de línea recta . . . . . . . . . . . . . . . . . . . . . . . .347


Capítulo 15: Uso de condicionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .355
Capítulo 16: Lazos de control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .367 Capítulo
17: Estructuras de control inusuales . . . . . . . . . . . . . . . . . . . . . . . . . . .391 Capítulo 18:
Métodos controlados por tablas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .411 Capítulo 19:
Problemas generales de control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .431
capitulo 14

Organización del código de línea


recta

cc2e.com/1465 Contenido

- 14.1 Declaraciones que deben estar en un orden específico: página 347

- 14.2 Sentencias cuyo orden no importa: página 351

Temas relacionados

- Temas generales de control: Capítulo 19

- Código con condicionales: Capítulo 15

- Código con bucles: Capítulo 16

- Alcance de variables y objetos: Sección 10.4, “Alcance”

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.

14.1 Declaraciones que deben estar en un orden específico


Las sentencias secuenciales más fáciles de ordenar son aquellas en las que el orden cuenta. Aquí hay un
ejemplo:

Ejemplo Java de sentencias en las que cuenta el orden


datos = LeerDatos();
resultados = CalcularResultadosDeDatos(datos);
ImprimirResultados(resultados);

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.

El concepto subyacente en este ejemplo es el de dependencias. La tercera afirmación depende


de la segunda, la segunda de la primera. En este ejemplo, el hecho de que uno

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:

Ejemplo Java de sentencias en las que el orden cuenta, pero no obviamente


ingresos.ComputeMonthly();
ingresos. Compute Quarterly ();
ingresos.ComputeAnnual();

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

Suponer queCalcular gastos de marketing()inicializa las variables miembro de la clase en las


que todas las demás rutinas colocan sus datos. En tal caso, debe llamarse antes que las otras
rutinas. ¿Cómo puedes saber eso al leer este código? Debido a que las llamadas de rutina no
tienen ningún parámetro, es posible que pueda adivinar que cada una de estas rutinas accede
a los datos de la clase. Pero no puede estar seguro al leer este código.

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

describe todas las funciones que realiza. En este caso,


ComputeMarketingExpenseEInitializeMemberData()sería un nombre adecuado. Podrías
decir que es un nombre terrible porque es muy largo, pero el nombre describe lo que
hace la rutina y no es terrible. ¡La rutina en sí es terrible!

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í:

Ejemplo de Visual Basic de datos que sugiere una dependencia de orden


InitializeExpenseData(gastData )
ComputeMarketingExpense( datos de gastos )
Calcular gastos de ventas ( datos de gastos)
Calcular gastos de viaje ( datos de gastos)
datos de gastos )
)
ComputePersonnelExpense(DisplayExpenseSummary(expenseData

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:

Ejemplo de Visual Basic de datos que no indican una dependencia de orden


Calcular gastos de marketing datos de marketing )
( Calcular gastos de ventas ( los datos de ventas )

Calcular gastos de viaje ( datos de viaje )


Calcular gastos de personal ( datospersonales )
Mostrar resumen de gastos (datos de marketing, datos de ventas, datos de viaje, datos de personal)

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.

Verifique las dependencias con aserciones o código de manejo de erroresSi el código es lo


suficientemente crítico, puede usar variables de estado y código de manejo de errores o aserciones
para documentar dependencias secuenciales críticas. Por ejemplo, en el constructor de la clase,
puede inicializar una variable miembro de la claseisExpenseDataInitializedafalso. luego en
InitializeExpenseData(), puede establecerisExpenseDataInitializedaverdadero. Cada función que
depende dedatos de gastossiendo inicializado puede comprobar siisExpenseDataInitializedse ha
establecido enverdaderoantes de realizar operaciones adicionales endatos de gastos. Dependiendo
de cuán extensas sean las dependencias, es posible que también necesite variables como
isMarketingExpenseComputed,isSalesExpenseComputed, y así.

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

14.2 Declaraciones cuyo orden no importa


Puede encontrar casos en los que parezca que el orden de algunas declaraciones o algunos bloques
de código no importa en absoluto. Una declaración no depende ni sigue lógicamente a otra
declaración. Pero el orden afecta la legibilidad, el rendimiento y la capacidad de mantenimiento, y en
ausencia de dependencias de orden de ejecución, puede usar criterios secundarios para determinar
el orden de las declaraciones o bloques de código. El principio rector es el Principio de Proximidad:
Mantener juntas las acciones relacionadas.

Hacer que el código se lea de arriba a abajo


Como principio general, haga que el programa se lea de arriba hacia abajo en lugar de saltar. Los
expertos están de acuerdo en que el orden de arriba hacia abajo contribuye más a la legibilidad.
Simplemente hacer que el control fluya de arriba a abajo en tiempo de ejecución no es suficiente. Si
alguien que está leyendo su código tiene que buscar en todo el programa para encontrar la
información necesaria, debe reorganizar el código. Aquí hay un ejemplo:

Ejemplo de C++ de código incorrecto que salta alrededor


Datos de marketing datos de marketing;
Los datos de ventas los datos de ventas;

Datos de viaje datos de viaje;

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:

Ejemplo de C++ de buen código secuencial que se lee de arriba a abajo


Datos de marketing Datos de marketing;
marketingData.ComputeQuarterly();
marketingData.ComputeAnnual();
marketingData.Print();
352 Capítulo 14: Organización del código de línea recta

datos de ventas datos de ventas;

salesData.ComputeQuarterly();
salesData.ComputeAnnual();
datosventas.Imprimir();

datos de viaje datos de viaje;


travelData.ComputeQuarterly();
travelData.ComputeAnnual();
travelData.Print();

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

“Medición del tiempo de vida de


pequeño. Y quizás lo más importante, ahora parece que el código podría dividirse en rutinas
una variable” en la Sección 10.4. separadas para marketing, ventas y datos de viajes. El primer fragmento de código no dio
indicios de que tal descomposición fuera posible.

Agrupar sentencias relacionadas


Referencia cruzadaSi sigue Ponga las declaraciones relacionadas juntas. Pueden estar relacionados porque operan con los
el proceso de programación
mismos datos, realizan tareas similares o dependen de que cada uno se realice en orden.
de pseudocódigo, su
el código se agrupará automáticamente
Una manera fácil de probar si las declaraciones relacionadas están bien agrupadas es imprimir una lista de
en sentencias relacionadas. Para obtener

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,

consulte la Sección 10.4, “Alcance”.


código para que las declaraciones relacionadas se agrupen mejor.
Puntos clave 353

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.

cc2e.com/1472 Lista de verificación: Organización del código de línea recta


- ¿El código hace que las dependencias entre declaraciones sean obvias?

- ¿Los nombres de las rutinas hacen obvias las dependencias?

- ¿Los parámetros de las rutinas hacen obvias las dependencias?

- ¿Los comentarios describen dependencias que de otro modo no estarían


claras?

- ¿Se han utilizado variables de limpieza para verificar dependencias secuenciales en


secciones críticas del código?

- ¿El código se lee de arriba a abajo?


- ¿Están agrupadas las declaraciones relacionadas?

- ¿Se han trasladado grupos de enunciados relativamente independientes a sus


propias rutinas?

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.

- Si el código no tiene dependencias de orden, mantenga las declaraciones relacionadas


lo más juntas posible.
Traducido del inglés al español - www.onlinedoctranslator.com
Capítulo 15

Uso de condicionales
cc2e.com/1538 Contenido

- 15.1siDeclaraciones: página 355

- 15.2casoDeclaraciones: página 361

Temas relacionados

- Domando el anidamiento profundo: Sección 19.4

- Cuestiones generales de control: Capítulo 19

- Código con bucles: Capítulo 16

- Código de línea recta: Capítulo 14

- Relación entre tipos de datos y estructuras de control: Sección 10.7

Un condicional es una sentencia que controla la ejecución de otras sentencias; la ejecución de


las otras declaraciones está "condicionada" a declaraciones tales comosi,más,caso, ycambiar.
Aunque lógicamente tiene sentido referirse a controles de bucle comotiempoyporcomo
condicionales también, por convención han sido tratados por separado. El capítulo 16, "Control
de bucles", examinarátiempoypordeclaraciones.

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

Siga estas pautas al escribirsideclaraciones:

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

"Resumen de técnicas para reducir el


de una decisión lo más cerca posible de la decisión. Aquí hay un ejemplo de código que hace una
anidamiento profundo" en la Sección 19.4. gran cantidad de procesamiento de errores, verificando al azar los errores en el camino:

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

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_Error) Entonces
Caso de error. tipoError = TipoError_DataSummaryError Else

caso nominal. PrintSummary(datos de resumen)


SaveSummaryData(datos de resumen, estado) If
(estado = Status_Error) Entonces
Caso de error. tipoError = TipoError_SummarySaveError Else

caso nominal. ActualizarTodasLasCuentas()


BorrarDeshacerArchivo()

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

tipo de error = Tipo de error_Ninguno


Más
Caso de error. tipo de error = ErrorType_SummarySaveError
Terminara si

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:

Ejemplo de Java de un nulosiCláusula


si (alguna prueba)
;
CODIFICACIÓN más {
HORROR
// hacer algo
...
}

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

booleana correcta para


fácilmente negando el predicado en elsideclaración, moviendo el código de lamáscláusula a la si
controlarlo. Para obtener detalles cláusula, y eliminando lamáscláusula. Así es como se vería el código después de esos cambios:
sobre el uso eficaz de expresiones

booleanas, consulte la Sección

19.1, “Expresiones booleanas”.

Ejemplo de Java de un nulo convertidosiCláusula


si (! alguna prueba) {
// hacer algo
...
}
358 Capítulo 15: Uso de condicionales

3 Considera elmáscláusula Si crees que necesitas un simplesideclaración, considerar


2
1
si en realidad no necesita unsi-entonces-otrodeclaración. Un análisis clásico de General Motors

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í:

Java Ejemplo de un útil, comentadomásCláusula


// si el color es valido
si (COLOR_MIN <= color && color <= COLOR_MAX) {
// hacer algo
...
}
más {
// si no, el color no es válido
// pantalla no escrita en –- ignorar el comando de forma segura
}

Prueba elmáscláusula de correcciónAl probar su código, podría pensar que la cláusula


principal, lasi, es todo lo que necesita ser probado. Si es posible probar elmás cláusula, sin
embargo, asegúrese de hacer eso.

Compruebe la inversión de lasiymáscláusulasUn error común en la programación.si


entoncess es voltear el código que se supone que sigue alsicláusula y el código que se supone
que sigue a lamáscláusula o para obtener la lógica de lasiprueba al revés. Verifique su código
para este error común.

cadenas desi-entonces-otroDeclaraciones

En idiomas que no admitencasoafirmaciones, o que las respaldan solo parcialmente, a menudo se


encontrará escribiendo cadenas desi-entonces-otropruebas Por ejemplo, el código para categorizar
un personaje podría usar una cadena como esta:

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

carácter de entrada == ':' ||


carácter de entrada == ';' ||
carácter de entrada == '?' ||
carácter de entrada == '-'
){
tipo de caracter = Tipo de carácter_Puntuación;
}
de lo contrario si ('0' <= carácter de entrada && carácter de entrada <= '9') {
tipo de caracter = tipo de caracter_digit;
}
más si (
( 'a' <= carácter de entrada && carácter de entrada <= 'z' ) || ( 'A' <=
carácter de entrada && carácter de entrada <= 'Z' )
){
tipoCaracter = TipoCaracter_Letra;
}

Tenga en cuenta estas pautas al escribir talessi-entonces-otrocadenas:

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:

C++ Ejemplo de unsi-entonces-otroCadena que utiliza llamadas a funciones booleanas


if (EsControl(caracter de entrada)) {
characterType = CharacterType_ControlCharacter;
}
más si (EsPuntuación(caracter de entrada)) {
characterType = CharacterType_Punctuation;
}
de lo contrario si (EsDígito(CarácterEntrada)) {
tipo de caracter = tipo de caracter_digit;
}
más si (IsLetter(inputCharacter)) {
tipoCaracter = TipoCaracter_Letra;
}

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:

Ejemplo de C++ de probar primero el caso más común


Esta prueba, la más común, if (EsLetra(CaracterEntrada)) {
ahora se hace primero. tipoCaracter = TipoCaracter_Letra;
}
360 Capítulo 15: Uso de condicionales

más si (EsPuntuación(caracter de entrada)) {


characterType = CharacterType_Punctuation;
}
de lo contrario si (EsDígito(CarácterEntrada)) {
tipo de caracter = tipo de caracter_digit;
}
Esta prueba, la menos común, ahora se más si (IsControl(inputCharacter)) {
hace en último lugar. characterType = CharacterType_ControlCharacter;
}

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.");
}

Reemplazarsi-entonces-otrocadenas con otras construcciones si su idioma las admite


Algunos lenguajes, Microsoft Visual Basic y Ada, por ejemplo, proporcionancasodeclaraciones
que admiten el uso de cadenas, enumeraciones y funciones lógicas. Úselos: son más fáciles de
codificar y de leer quesi-entonces-otrocadenas Código para clasificar tipos de caracteres
usando uncasodeclaración en Visual Basic se escribiría así:

Ejemplo de Visual Basic del uso de uncasoDeclaración en lugar de unasi-entonces-otroCadena


Seleccionar carácter de entrada de mayúsculas y minúsculas

Caso "a" a "z"


tipoCaracter = TipoCaracter_Letra
Caso " ", ",", ".", "!", "(", ")", ":", ";", "?", "-"
tipo de caracter = Tipo de carácter_Puntuación
Caso "0" a "9"
tipo de caracter = Tipo de carácter_dígito
Caso FIRST_CONTROL_CHARACTER a LAST_CONTROL_CHARACTER
characterType = CharacterType_Control Case Else

DisplayInternalError( "Se detectó un tipo de carácter inesperado". ) End Select


15.2
1 caso Declaraciones 361

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.

Las siguientes secciones presentan pautas para el usocasodeclaraciones de manera efectiva:

Elección de la ordenación de casos más efectiva


Puede elegir entre una variedad de formas de organizar los casos en uncasodeclaración. Si tienes un
pequeñocasodeclaración con tres opciones y tres líneas de código correspondientes, el orden que
use no importa mucho. Si tienes muchocasodeclaración, por ejemplo, unacasoLa declaración que
maneja docenas de eventos en un orden de programa controlado por eventos es significativa. Las
siguientes son algunas posibilidades de pedido:

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.

Consejos para usarcasoDeclaraciones

Aquí hay varios consejos para usarcasodeclaraciones:

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

Capítulo 24, "Refactorización".

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.

No inventes variables falsas para poder usar elcasodeclaraciónAcasoLa declaración debe


usarse para datos simples que se clasifican fácilmente. Si sus datos no son simples, use
cadenas desi-entonces-otroen su lugar. Las variables falsas son confusas y debe evitarlas. Por
ejemplo, no hagas esto:
362 Capítulo 15: Uso de condicionales

Ejemplo de Java para crear un falsocasoVariable: mala práctica


acción = comandoUsuario[ 0 ];
cambiar (acción) {
CODIFICACIÓN caso 'c':
HORROR
Copiar();
descanso;

caso 'd':
EliminarCarácter();
descanso;

caso 'f':
Formato();
descanso;

caso 'h':
Ayuda();
descanso;

...
defecto:
HandleUserInputError(ErrorType.InvalidUserCommand);
}

La variable que controla elcasodeclaración esacción. En este caso,acciónse crea quitando el


primer carácter de lacomando de usuariostring, una cadena ingresada por el usuario.

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í:

Ejemplo de uso de Javasi-entonces-otros en lugar de un falsocasoVariable: buena práctica


if (UserCommand.equals( COMMAND_STRING_COPY ) ) {
Copiar();
}
de lo contrario si (UserCommand.equals( COMMAND_STRING_DELETE ) ) {
EliminarCarácter();
}
de lo contrario si ( UserCommand.equals ( COMMAND_STRING_FORMAT ) ) {
Formato();
}
de lo contrario si ( UserCommand.equals ( COMMAND_STRING_HELP ) ) {
Ayuda();
}
...
más {
HandleUserInputError(ErrorType_InvalidCommandInput);
}
15.2
1 caso Declaraciones 363

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.

Talcasolas declaraciones se descomponen bajo la modificación. Si usa un valor predeterminado legítimo,


agregar un nuevo caso es trivial: solo agrega el caso y el código correspondiente. Si usa un valor
predeterminado falso, la modificación es más difícil. Debe agregar el nuevo caso, posiblemente
convirtiéndolo en el nuevo predeterminado, y luego cambiar el caso utilizado anteriormente como
predeterminado para que sea un caso legítimo. Utilice un valor predeterminado legítimo en primer lugar.

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.

En C++ y Java, evite pasar por el final de uncasodeclaraciónLos lenguajes similares a


C (C, C++ y Java) no se separan automáticamente de cada caso. En su lugar, debe
codificar el final de cada caso explícitamente. Si no codifica el final de un caso, el
364 Capítulo 15: Uso de condicionales

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:

C++ Ejemplo de Abuso de lacasoDeclaración


cambiar (var de entrada) {
caso 'A': si ( prueba ) {
CODIFICACIÓN

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”.

Esta práctica es mala porque entremezcla construcciones de control. Las construcciones de


control anidadas son lo suficientemente difíciles de entender; las construcciones superpuestas
son casi imposibles. Modificaciones de caso'A'o caso'B'será más difícil que la cirugía cerebral, y
es probable que los casos deban limpiarse antes de que las modificaciones funcionen.
También podrías hacerlo bien la primera vez. En general, es una buena idea evitar caer al final
de uncasodeclaración.

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.

Ejemplo en C++ de cómo documentar la caída por el final de uncasoDeclaración


cambiar (nivel de documentación de error) {
caso DocumentationLevel_Full:
MostrarDetallesError(númeroError);
// FALLTHROUGH -- La documentación completa también imprime comentarios resumidos

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.

cc2e.com/1545 LISTA DE VERIFICACIÓN: uso de condicionales

si-entoncesDeclaraciones
- ¿Está clara la ruta nominal a través del código?

- Hacersi-entonceslas pruebas se bifurcan correctamente en la igualdad?

- Es elmáscláusula presente y documentada?

- Es elmáscláusula correcta?

- son lossiymás¿Cláusulas usadas correctamente, no invertidas?

- ¿El caso normal sigue elsien lugar de lamás?

si-entonces-otro-siCadenas
- ¿Las pruebas complicadas están encapsuladas en llamadas a funciones booleanas?

- ¿Se analizan primero los casos más comunes?

- ¿Están cubiertos todos los casos?

- Es elsi-entonces-otro-siencadenar la mejor implementación, mejor que uncaso


¿declaración?

casoDeclaraciones
- ¿Los casos están ordenados de manera significativa?

- ¿Son simples las acciones para cada caso, llamando a otras rutinas si es necesario?

- ¿Elcasodeclaración de prueba una variable real, no una falsa que se inventó


únicamente para usar y abusar de lacaso¿declaración?

- ¿Es legítimo el uso de la cláusula default?

- ¿Se utiliza la cláusula por defecto para detectar y reportar casos inesperados?

- En C, C++ o Java, ¿el final de cada caso tiene undescanso?


366 Capítulo 15: Uso de condicionales

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.

- Parasi-entonces-otrocadenas ycasoinstrucciones, elija un orden que maximice la


legibilidad.

- 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

- 16.1 Selección del tipo de bucle: página 367

- 16.2 Control del bucle: página 373

- 16.3 Creación de bucles fácilmente: de adentro hacia afuera: página 385

- 16.4 Correspondencia entre lazos y arreglos: página 387

Temas relacionados

- Domando el anidamiento profundo: Sección 19.4

- Cuestiones generales de control: Capítulo 19

- Código con condicionales: Capítulo 15

- Código de línea recta: Capítulo 14

- Relación entre estructuras de control y tipos de datos: Sección 10.7

"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.

16.1 Selección del tipo de bucle


En la mayoría de los idiomas, usará algunos tipos de bucles:

- El bucle contado se realiza un número específico de veces, quizás una vez para
cada empleado.

- El bucle evaluado continuamente no sabe de antemano cuántas veces se ejecutará y


prueba si ha terminado en cada iteración. Por ejemplo, se ejecuta mientras queda
dinero, hasta que el usuario seleccione salir o hasta que encuentre un error.

- 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.

La flexibilidad y la ubicación de la prueba determinan el tipo de bucle a elegir como estructura de


control. La Tabla 16-1 muestra los tipos de bucles en varios idiomas y describe la flexibilidad y la
ubicación de prueba de cada bucle.

Tabla 16-1 Tipos de bucles


Idioma tipo de bucle Flexibilidad Ubicación de prueba

básico visual Para-Siguiente rígido comienzo


Mientras-Wend flexible comienzo
Do-Loop-Mientras flexible principio o fin
Para cada rígido comienzo
C, C++, C#, Java por flexible comienzo
tiempo flexible comienzo
hacer mientras flexible final
para cada* rígido comienzo
* Disponible solo en C#. Planeado para otros lenguajes, incluido Java, en el momento de escribir este artículo.

Cuándo usar untiempoCírculo


Los programadores novatos a veces piensan que untiempobucle se evalúa continuamente y
que termina en el instante en que eltiempola condición se vuelve falsa, independientemente
de qué declaración en el bucle se esté ejecutando (Curtis et al. 1986). Aunque no es tan
flexible, untiempoloop es una opción de bucle flexible. Si no sabe con anticipación
exactamente cuántas veces deseará que el bucle se itere, use untiempocírculo. Al contrario de
lo que piensan algunos novatos, la prueba para la salida del bucle se realiza solo una vez cada
vez que se pasa por el bucle, y el problema principal con respecto atiempobucles es decidir si
probar al principio o al final del bucle.
16.1 Selección del tipo de bucle 369

Bucle con prueba al principio


Para un ciclo que prueba al principio, puede usar untiempoloop en C++, C#, Java, Visual Basic y
la mayoría de los demás lenguajes. Puede emular untiempobucle en otros idiomas.

Bucle con prueba al final


Ocasionalmente, es posible que tenga una situación en la que desee un ciclo flexible, pero el
ciclo debe ejecutarse al menos una vez. En tal caso, puede utilizar untiempobucle que se
prueba en su extremo. Puedes usarhacer mientrasen C++, C# y Java,Do-Loop-Mientrasen
Visual Basic, o puede emular bucles probados en otros idiomas.

Cuándo usar un bucle con salida


Un bucle con salida es un bucle en el que la condición de salida aparece en medio del
bucle en lugar de al principio o al final. El bucle de bucle con salida está disponible
explícitamente en Visual Basic y puede emularlo con las construcciones estructuradas
tiempo ydescansoen C++, C y Java o conirS en otros idiomas.

Bucle normal con bucles de salida

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:

Ejemplo de Visual Basic de un bucle genérico con bucle de salida


Hacer

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:

Ejemplo en C++ de código duplicado que se descompondrá durante el mantenimiento


// Calcular puntajes y calificaciones.
puntuación = 0;
Estas líneas aparecen aquí... GetNextRating( &ratingIncrement ); rating =
rating + ratingIncrement;
while ((puntuación <puntajeobjetivo) && (incremento de calificación!= 0)) {
ObtenerPuntuaciónSiguiente( &puntuaciónIncremento );
puntaje = puntaje + incremento de puntaje;

…y se repiten aquí. GetNextRating( &ratingIncrement ); rating = rating +


ratingIncrement;

}
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:

Ejemplo en C++ de un bucle con salida que es más fácil de mantener


// Calcular puntajes y calificaciones. El código usa un bucle infinito // y una
declaración de interrupción para emular un bucle con salida. puntuación = 0;

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;

usando los Teoremas de DeMorgan, }


descritos en

Sección 19.1). ObtenerPuntuaciónSiguiente( &puntuaciónIncremento );

puntaje = puntaje + incremento de puntaje;


}

Así es como se escribe el mismo código en Visual Basic:

Ejemplo de Visual Basic de un bucle con salida


' Calcular puntajes y puntajes de
calificación = 0
Hacer

GetNextRating(ratingIncrement) rating =
rating + ratingIncrement

If ( not ( score < targetScore and ratingIncrement <> 0 ) ) Then Exit Do

GetNextScore( ScoreIncrement ) score =


score + scoreIncrement Loop

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

detalles sobre el uso de comentarios con


prueba.
bucles, consulte "Estructuras de control de

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.

Bucle anormal con bucles de salida

Aquí se muestra otro tipo de bucle con salida que se usa para evitar un bucle y medio:

Ejemplo en C++ de ingreso a la mitad de un bucle con unir-Mala práctica


ir a Inicio;
while ( expresión ) {
CODIFICACIÓN // hacer algo
HORROR
...

Comienzo:

// hacer algo más . . .

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.

C++ Ejemplo de código reescrito sin unir—Mejor práctica


mientras (verdadero) {
Los bloques antes y después de // hacer algo más . . .
ladescansohan sido cambiados.
372 Capítulo 16: Lazos de control

si ( !( expresión ) ) {
descanso;

// hacer algo
...
}

Cuándo usar unporCírculo


Otras lecturasPara obtener más Aporloop es una buena opción cuando necesita un bucle que se ejecute un número específico de
buenas pautas sobre el usopor
veces. Puedes usarporen C++, C, Java, Visual Basic y la mayoría de los demás lenguajes.
bucles, verEscribir código sólido(

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.

Cuándo usar unpara cadaCírculo


lospara cadabucle o su equivalente (para cadaCía#,Para cadaen Visual Basic,para adentroen
Python) es útil para realizar una operación en cada miembro de una matriz u otro contenedor.
Tiene la ventaja de eliminar la aritmética de mantenimiento de bucles y, por lo tanto, elimina
cualquier posibilidad de errores en la aritmética de mantenimiento de bucles. He aquí un ejemplo
de este tipo de bucle:

C# Ejemplo de unpara cadaCírculo


int [] fibonacciSequence = new int [] { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 }; int numerosfibonacci impares = 0;

int números pares de Fibonacci = 0;

// 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++;
}
}

Console.WriteLine( "Se encontraron {0} números impares y {1} números pares.",


números de Fibonacci impares, números de Fibonacci pares);
16.2 Control del bucle 373

16.2 Control del bucle


¿Qué puede salir mal con un bucle? Cualquier respuesta tendría que incluir inicialización de bucle
incorrecta u omitida, inicialización omitida de acumuladores u otras variables relacionadas con el
bucle, anidamiento incorrecto, terminación incorrecta del bucle, olvido de incrementar una variable
de bucle o incrementar la variable incorrectamente e indexar un elemento de matriz de un índice de
bucle incorrectamente.

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

condición de salida está dentro de

la caja negra. Incluso si usa solo

una condición de salida, pierde el

beneficio de tratar el ciclo como }


una caja negra.

¿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

el alcance de las variables de índice de


modificar el código de inicialización. El mismo tipo de error puede ocurrir cuando mueve o copia el
bucle al propio bucle" más adelante en código de bucle en una rutina diferente sin mover o copiar su código de inicialización. Poner las
este capítulo.
inicializaciones lejos del bucle, en la sección de declaración de datos o en una sección de limpieza en
la parte superior de la rutina que contiene el bucle, invita a problemas de inicialización.
374 Capítulo 16: Lazos de control

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.

losmientras (verdadero)idiom se considera una forma estándar de escribir un bucle infinito en


C++, Java, Visual Basic y otros lenguajes que admiten estructuras comparables. Algunos
programadores prefieren usarpor( ;; ), que es una alternativa aceptada.

Preferirporbucles cuando son apropiadoslosporloop empaqueta el código de control de bucle en un solo


lugar, lo que hace que los bucles sean fáciles de leer. Un error que suelen cometer los programadores al
modificar el software es cambiar el código de inicialización del ciclo en la parte superior de untiempoloop
pero olvidándose de cambiar el código relacionado en la parte inferior. en unporbucle, todo el código
relevante está junto en la parte superior del bucle, lo que facilita las modificaciones correctas. Si puedes usar
elporbucle apropiadamente en lugar de otro tipo de bucle, hazlo.

No uses unporbucle cuando untiempobucle es más apropiadoUn abuso común de lo


flexibleporestructura de bucle en C++, C# y Java está llenando al azar el contenido de untiempo
bucle en unporencabezado de bucle El siguiente ejemplo muestra untiempobucle embutido en
unporencabezado de bucle:

C++ Ejemplo de untiempobucle abusivamente abarrotado en unporEncabezado de bucle


// lee todos los registros de un archivo
for ( inputFile.MoveToStart(), recordCount = 0; !inputFile.EndOfFile();
CODIFICACIÓN cuentaregistro++ ) {
HORROR
archivo de entrada.GetRecord();
}

La ventaja de C++porbucle sobreporloops en otros lenguajes es que es más flexible en cuanto a


los tipos de información de inicialización y terminación que puede usar. La debilidad inherente
a tal flexibilidad es que puede colocar declaraciones en el encabezado del bucle que no tienen
nada que ver con el control del bucle.

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

C++ Ejemplo de uso lógico si no convencional de unporEncabezado de bucle


cuentaregistro = 0;
para ( archivo de entrada. MoveToStart (); ! archivo de entrada. EndOfFile (); archivo de entrada. GetRecord () ) {
registroCuenta++;
}

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:

C++ Ejemplo de uso apropiado de untiempoCírculo


// leer todos los registros de un archivo
inputFile.MoveToStart();
cuentaregistro = 0;
mientras (! archivo de entrada. EndOfFile ()) {
archivo de entrada.GetRecord();
registroCuenta++;
}

Procesando la mitad del bucle


Las siguientes subsecciones describen el manejo de la mitad de un bucle:

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:

Ejemplo en C++ de un bucle vacío


while ( ( charEntrada = archivo de datos.GetChar() ) != CharType_Eof ) {
;
}
376 Capítulo 16: Lazos de control

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:

Ejemplo en C++ de un bucle vacío convertido en un bucle ocupado


hacer {
inputChar = archivo de datos.GetChar(); } while
(inputChar != CharType_Eof );

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:

Ejemplo de C++ de declaraciones de limpieza al final de un bucle


nombreCuenta = 0;
largo total = 0;
mientras (! archivo de entrada. EndOfFile ()) {
// hacer el trabajo del bucle inputFile >>
inputString; nombres[ nameCount ] =
inputString; . . .

// prepararse para el próximo paso por el ciclo--servicio de limpieza


Aquí están las declaraciones nameCount++;
de limpieza. longitudTotal = longitudTotal + cadenaEntrada.longitud();
}

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

ajuste de código", y el Capítulo 26,


rutinas en el sentido de que cada uno debe hacer una sola cosa y hacerlo bien. Si parece ineficiente
"Técnicas de ajuste de código". usar dos bucles donde uno sería suficiente, escriba el código como dos bucles, comente que podrían
combinarse para lograr eficiencia y luego espere hasta que los puntos de referencia muestren que la
sección del programa presenta un problema de rendimiento antes de cambiar los dos bucles. en uno.
16.2 Control del bucle 377

Saliendo del bucle


Estas subsecciones describen el manejo del final de un bucle:

Asegúrate de que el bucle terminaEsto es fundamental. Simule mentalmente la ejecución del


bucle hasta que esté seguro de que, en todas las circunstancias, finaliza. Piense en los casos
nominales, los puntos finales y cada uno de los casos excepcionales.

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:

Ejemplo de Java de Monkeying con un índice de bucle


para (int i = 0; i < 100; i++) {
// algún código
CODIFICACIÓN ...
HORROR
si ( ... ) {
Aquí está el mono. yo = 100;
}

// 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 );
}

En este fragmento, la segunda prueba pararecordCount <MaxRecordshace que parezca


que se supone que el ciclo debe recorrer todos los valores enentrada[]y volververdadero
si encuentra el igual avalor de pruebayfalsode lo contrario. Es difícil recordar si el índice
se incrementa más allá del final del ciclo, por lo que es fácil cometer un error de uno. Es
mejor escribir código que no dependa del valor final del índice. He aquí cómo reescribir
el código:

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.

Este ciclo de C++ podría usar de manera rentable un contador de seguridad:


16.2 Control del bucle 379

Ejemplo en C++ de un bucle que podría usar un contador de seguridad


hacer {
nodo = nodo->Siguiente;
...
} while (nodo->Siguiente!= NULL);

Aquí está el mismo código con los contadores de seguridad agregados:

Ejemplo de C++ de uso de un contador de seguridad


Contador de seguridad = 0;
hacer {
nodo = nodo->Siguiente;
...
Aquí está el código del contador de contadordeseguridad++;
seguridad. if (contadordeseguridad >= LÍMITE_DE_SEGURIDAD) {
Assert( false, "Error interno: Violación del contador de seguridad". );
}
...
} while (nodo->Siguiente!= NULL);

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

propenso a producir errores más tarde que cualquier otro código.

Salir temprano de los bucles

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.

losSeguirdeclaración es similar adescansoen el sentido de que es una declaración de control


de bucle auxiliar. Sin embargo, en lugar de provocar una salida de bucle,Seguirhace que el
programa salte el cuerpo del ciclo y continúe ejecutándose al comienzo de la siguiente
iteración del ciclo. ASeguirdeclaración es la abreviatura de unsi-entoncescláusula que evitaría
que se ejecutara el resto del ciclo.

Considere usardescansodeclaraciones en lugar de banderas booleanas en untiempocírculoEn algunos


casos, agregar banderas booleanas a untiempoloop para emular las salidas del cuerpo del loop hace que el
loop sea difícil de leer. A veces, puede eliminar varios niveles de sangría dentro de un bucle y simplificar el
control del bucle simplemente usando undescansoen lugar de una serie desipruebas
380 Capítulo 16: Lazos de control

Poniendo múltiplesdescansocondiciones en sentencias separadas y colocándolas cerca del código


que produce eldescansopuede reducir el anidamiento y hacer que el bucle sea más legible.

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):

C++ Ejemplo de uso erróneo de undescansoDeclaración dentro de unhacer-cambiar-siBloquear


hacer {
...
cambiar
...
si () {
...
Estedescansoestaba destinado descanso;

a lasipero salió de la cambiar ...


en cambio. }
...
} tiempo ( ... );

Múltipledescansos no necesariamente indican un error, pero su existencia en un bucle es una señal de


advertencia, un canario en una mina de carbón que está jadeando por aire en lugar de cantar tan fuerte
como debería ser.

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:

Ejemplo de pseudocódigo de un uso relativamente seguro deSeguir


while (no eof(archivo)) hacer
leer (registro, archivo)
if (registro.Tipo <> tipoobjetivo) entonces
Seguir

- - registro de proceso de targetType


...
final tiempo

UsandoSeguirde esta manera le permite evitar unasiprueba que sangraría efectivamente


todo el cuerpo del bucle. Si, por el contrario, elSeguirocurre hacia la mitad o el final del
bucle, use unsien cambio.
16.2 Control del bucle 381

Utilice la etiquetadescansoestructura si su idioma lo admiteJava admite el uso de etiquetasdescansos


para evitar el tipo de problema experimentado con el apagón telefónico de la ciudad de Nueva York. un
etiquetadodescansose puede utilizar para salir de unporbucle, unsiinstrucción, o cualquier bloque de código
encerrado entre llaves (Arnold, Gosling y Holmes 2000).

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:

Ejemplo de Java de un mejor uso de un etiquetadodescansoDeclaración dentro de un hacer-


cambiar-siBloquear
hacer {
...
cambiar
...
CALL_CENTER_DOWN:
si () {
...
El objetivo de la etiqueta romper CALL_CENTER_DOWN;
descansoes inequívoco. ...
}
...
} tiempo ( ... );

UsardescansoySeguirsolo con precauciónUso dedescansoelimina la posibilidad de tratar un


bucle como una caja negra. Limitarse a una sola declaración para controlar la condición de
salida de un bucle es una forma poderosa de simplificar sus bucles. Usando undescansoobliga
a la persona que lee su código a mirar dentro del bucle para comprender el control del bucle.
Eso hace que el bucle sea más difícil de entender.

Usardescansosólo después de haber considerado las alternativas. No sabes con certeza si


Seguirydescansoson construcciones virtuosas o malvadas. Algunos informáticos argumentan
que son una técnica legítima en la programación estructurada; algunos argumentan que no lo
son. Porque no sabes en general siSeguirydescanso son correctos o incorrectos, utilícelos, pero
solo con el temor de que pueda estar equivocado. Realmente es una proposición simple: si no
puedes defender unadescansoo unSeguir, no lo uses.

Comprobación de puntos finales

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.

Uso de variables de bucle

Aquí hay algunas pautas para usar variables de bucle:

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:

Ejemplo de Java de nombres de variables de bucle incorrectos

for ( int i = 0; i < numPayCodes; i++ ) {


para (int j = 0; j < 12; j++) {
CODIFICACIÓN for ( int k = 0; k < numDivisiones; k++ ) {
HORROR
suma = suma + transacción[ j ][ i ][ k ];
}
}
}
16.2 Control del bucle 383

¿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:

Ejemplo de Java de buenos nombres de variables de bucle


for ( int payCodeIdx = 0; payCodeIdx < numPayCodes; payCodeIdx++ ) {
for (int mes = 0; mes < 12; mes++) {
for ( int divisionIdx = 0; divisionIdx < numDivisions; divisionIdx++ ) {
suma = suma + transacción[ mes ][ payCodeIdx ][ divisionIdx ];
}
}
}

¿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:

Ejemplo de C++ de diafonía de índices


ise usa primero aquí... for ( i = 0; i < numPayCodes; i++ ) {
// mucho código
...
para ( j = 0; j < 12; j++ ) {
// mucho código
...
. . . y de nuevo aquí. for ( i = 0; i < numDivisiones; i++ ) {
suma = suma + transacción[ j ][ i ][ k ];
}
}
}

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

384 Capítulo 16: Lazos de control

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 una variable de índice de bucle dentro de unporcírculo


por (En tcuentaregistro = 0; recordCount < MAX_REGISTROS; cuentaregistro++ ) {
// código de bucle que usa recordCount
}

En principio, esta técnica debería permitir la creación de código que redeclaranúmero de


registros en múltiples bucles sin ningún riesgo de mal uso de los dos diferentesrecordCounts.
Ese uso daría lugar a un código que se ve 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 primer compilador marcadonúmero de registrosen el segundoporbucle para múltiples


declaraciones de variables y generó un error.

- El segundo compilador aceptónúmero de registrosen el segundoporloop pero permitió que se


usara fuera del primerporcírculo.

- 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

¿Cuánto debe durar un bucle?


La longitud del bucle se puede medir en líneas de código o profundidad de anidamiento. Aquí hay
algunas pautas:

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

anidamiento peligrosamente profundo”.


de anidamiento (Yourdon 1986a). Si va más allá de esa cantidad de niveles, acorte el bucle
(conceptualmente) dividiendo parte de él en una rutina o simplificando la estructura de control.

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.

16.3 Creación de bucles fácilmente: de adentro hacia afuera


Si a veces tiene problemas para codificar un bucle complejo, lo que la mayoría de los programadores tienen,
puede usar una técnica simple para hacerlo bien la primera vez. Aquí está el proceso general. Comience con
un caso. Codifique ese caso con literales. Luego, sangra, coloca un bucle alrededor y reemplaza los literales
con índices de bucle o expresiones calculadas. Ponga otro ciclo alrededor de eso, si es necesario, y
reemplace más literales. Continúe el proceso todo el tiempo que sea necesario. Cuando termine, agregue
todas las inicializaciones necesarias. Dado que comienza en el caso simple y trabaja hacia afuera para
generalizarlo, podría pensar en esto como una codificación de adentro hacia afuera.

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

Capítulo 9, “La programación del


la prima total del seguro de vida para un grupo. Necesita un ciclo que tome la tasa de cada persona
pseudocódigo”. en una lista y la agregue a un total. Así es como lo harías.
Proceso."
386 Capítulo 16: Lazos de control

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.

Paso 1: crear un bucle de adentro hacia afuera (ejemplo de pseudocódigo)


- - obtener la tasa de la tabla
- - añadir tasa al total

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.

Paso 2: crear un bucle de adentro hacia afuera (ejemplo de pseudocódigo)


mesaaún no tiene tarifa = tabla[ ]
índices. tasatotal = tasatotal + tasa

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 continuación, introduzca índices para elmesaformación:

Paso 3: crear un bucle de adentro hacia afuera (ejemplo de pseudocódigo)


tasa = tabla[ censo.Edad ][ censo.Género ] tasatotal =
tasatotal + tasa

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.

Paso 4: crear un bucle de adentro hacia afuera (ejemplo de pseudocódigo)


Para persona = primera persona a última persona
tasa = tabla[ censo.Edad, censo.Género ] tasatotal =
tasatotal + tasa
Terminar por

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

Paso 5: crear un bucle de adentro hacia afuera (ejemplo de pseudocódigo)


Para persona = primera persona a última persona
tasa = tabla[ censo[ persona ].Edad, censo[ persona ].género ] tasatotal = tasatotal +
tasa
Terminar por

Finalmente, escriba las inicializaciones que sean necesarias. En este caso, eltasa totalla variable
necesita ser inicializada.

Paso final: crear un bucle de adentro hacia afuera (ejemplo de pseudocódigo)


tasa total = 0
Para persona = primera persona a última persona
tasa = tabla[ censo[ persona ].Edad, censo[ persona ].género ] tasatotal = tasatotal +
tasa
Terminar por

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.

16.4 Correspondencia entre lazos y arreglos


Referencia cruzadaPara obtener Los bucles y las matrices a menudo están relacionados. En muchos casos, se crea un bucle para realizar una
más información sobre la
manipulación de matriz y los contadores de bucle se corresponden uno a uno con los índices de matriz. Por
correspondencia entre bucles y
matrices, consulte la Sección 10.7,
ejemplo, estos Javaporlos índices de bucle corresponden a los índices de matriz:
“Relación entre tipos de datos y
control”.
Ejemplo de Java de una multiplicación de matrices
Estructuras.”
for ( int fila = 0; fila < maxRows; fila++ ) {
for ( int columna = 0; columna < maxCols; columna++ ) {
producto[fila][columna] = a[fila][columna] * b[fila][columna];
}
}

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:

Ejemplo APL de una multiplicación de matrices


producto <-axb
388 Capítulo 16: Lazos de control

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.

cc2e.com/1616 LISTA DE VERIFICACIÓN: Bucles

Selección y creación de bucles


- Es untiempobucle utilizado en lugar de unporbucle, en su caso?

- ¿Se creó el bucle de adentro hacia afuera?

Entrar en el bucle
- ¿Se ingresa al bucle desde la parte superior?

- ¿Está el código de inicialización directamente antes del bucle?

- Si el bucle es un bucle infinito o un bucle de eventos, ¿está construido limpiamente


en lugar de usar una chapuza comopara i = 1 a 9999?

- Si el bucle es C++, C o Javaporbucle, ¿el encabezado del bucle está reservado para el código de control
de bucle?

Dentro del bucle


- ¿Utiliza el bucle{y}o su equivalente para encerrar el cuerpo del bucle y evitar
problemas derivados de modificaciones inadecuadas?

- ¿El cuerpo del bucle tiene algo dentro? ¿No está vacío?

- ¿Están agrupadas las tareas domésticas, ya sea al principio o al final del


ciclo?

- ¿El bucle realiza una y sólo una función, como lo hace una rutina bien
definida?

- ¿El ciclo es lo suficientemente corto para verlo todo a la vez?

- ¿El bucle está anidado en tres niveles o menos?

- ¿Se han movido los contenidos de los bucles largos a su propia rutina?

- Si el bucle es largo, ¿es especialmente claro?


Puntos clave 389

Í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?

- ¿Es el índice de bucle un tipo ordinal o un tipo enumerado, no de punto


flotante?

- ¿El índice de bucle tiene un nombre significativo?

- ¿El bucle evita la diafonía de índice?

Saliendo del bucle


- ¿Termina el bucle en todas las condiciones posibles?

- ¿El ciclo utiliza contadores de seguridad, si ha instituido un estándar de contador de


seguridad?

- ¿Es obvia la condición de terminación del bucle?

- SidescansooSeguirse utilizan, son correctos?

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.

- Piense detenidamente en el bucle para verificar que funciona normalmente en cada


caso y termina en todas las condiciones posibles.
capitulo 17

Estructuras de control inusuales


cc2e.com/1778 Contenido

- 17.1 Devoluciones múltiples de una rutina: página 391

- 17.2 Recursión: página 393

- 17.3ir: página 398

- 17.4 Perspectiva sobre estructuras de control inusuales: página 408

Temas relacionados

- Cuestiones generales de control: Capítulo 19

- Código de línea recta: Capítulo 14

- Código con condicionales: Capítulo 15

- Código con bucles: Capítulo 16

- Manejo de excepciones: Sección 8.4

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.

17.1 Devoluciones Múltiples de una Rutina


La mayoría de los idiomas admiten algunos medios para salir de una rutina a la mitad de la rutina.
losdevolverysalidaLas declaraciones son construcciones de control que permiten que un programa
salga de una rutina a voluntad. Hacen que la rutina termine a través del canal de salida normal,
devolviendo el control a la rutina de llamada. La palabradevolverse utiliza aquí como un término
genérico paradevolveren C++ y Java,Salir de SubyFunción de salidaen Microsoft Visual Basic y
construcciones similares. Estas son las pautas para usar eldevolverdeclaración:

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

El siguiente es un buen ejemplo de un caso en el que tiene sentido regresar de varios


lugares en una rutina:

Ejemplo en C++ de un buen retorno múltiple de una rutina


Esta rutina devuelve un Comparación Comparar( int valor1, int valor2 ) {
Comparaciónenumerado si ( valor1 < valor2 ) {
escribe. devuelve Comparación_Menor que;
}
si no ( valor1 > valor2 ) {
volver Comparación_mayor que;
}
devuelve Comparación_Igual;
}

Otros ejemplos son menos claros, como ilustra la siguiente subsección.

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í:

Código de Visual Basic que oscurece el caso nominal


Si archivo.nombreValido() Después

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

Aplicar sangría al cuerpo principal de la rutina dentro de cuatrosideclaraciones es estéticamente feo,


especialmente si hay mucho código dentro de la más internasideclaración. En tales casos, el flujo del
código a veces es más claro si los casos erróneos se verifican primero, despejando el camino para la
ruta nominal a través del código. Así es como podría verse:

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

Si no es clave de cifrado.válida() Entonces


errorStatus = FileError_InvalidEncryptionKey Salir de Sub

Terminara si

Si no es un archivo. Descifrar (clave de cifrado) Entonces


errorStatus = FileError_CantDecryptFile Salir Sub

Terminara si

Este es el código para el 'mucho código


caso nominal. ...

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

parte es fácil de descomponer en partes más pequeñas.

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

Ejemplo de Java de un algoritmo de clasificación que utiliza recursividad


void QuickSort( int firstIndex, int lastIndex, String [] nombres ) {
if (últimoÍndice > primerÍndice) {
int midPoint = Partition( firstIndex, lastIndex, nombres ); QuickSort
Aquí están las llamadas recursivas. (primerÍndice, punto medio-1, nombres);
QuickSort (punto medio + 1, último índice, nombres)
}
}

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

Sube porque la izquierda


no está disponible.

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

El código recursivo se ve así:

Ejemplo de C++ de moverse a través de un laberinto recursivamente


bool FindPathThroughMaze( Laberinto laberinto, Posición del punto ) {
// si la posición ya se intentó, no vuelva a intentarlo if (YaProbado(laberinto,
posición)) {
falso retorno;
}

// si esta posición es la salida, declarar éxito if


( ThisIsTheExit( maze, position ) ) {
devolver verdadero;
}

// recuerda que esta posición ha sido probada


RememberPosition( laberinto, posición );

// comprueba los caminos a la izquierda, arriba, abajo y a la derecha; si // alguna ruta


es exitosa, deja de buscar
if ( MoverIzquierda( laberinto, posición, &nuevaPosición ) ) {
if (FindPathThroughMaze(maze, newPosition)) {
devolver verdadero;

}
}

if ( MoverArriba( laberinto, posición, &nuevaPosición ) ) {


if (FindPathThroughMaze(maze, newPosition)) {
devolver verdadero;

}
}

if ( MoverAbajo( laberinto, posición, &nuevaPosición ) ) {


if (FindPathThroughMaze(maze, newPosition)) {
devolver verdadero;

}
}

if ( MoverDerecha( laberinto, posición, &nuevaPosición ) ) {


if (FindPathThroughMaze(maze, newPosition)) {
devolver verdadero;
}
}
falso retorno;
}

La primera línea de código comprueba si la posición ya se ha probado. Un objetivo clave al


escribir una rutina recursiva es la prevención de recursividad infinita. En este caso, si no
compruebas haber probado un punto, puedes seguir intentándolo infinitamente.

La segunda declaración verifica si la posición es la salida del laberinto. Si EstaEsLaSalida()


devolucionesverdadero, la rutina misma vuelveverdadero.
396 Capítulo 17: Estructuras de control inusuales

La tercera declaración recuerda que la posición ha sido visitada. Esto evita la


recursividad infinita que resultaría de una trayectoria circular.

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.

Consejos para usar la recursividad

Tenga en cuenta estos consejos cuando use la recursividad:

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

En este caso, si la rutina supera el límite de seguridad, deja de repetirse.

Si no desea pasar el contador de seguridad como un parámetro explícito, puede usar


una variable miembro en C++, Java o Visual Basic, o el equivalente en otros lenguajes.

Limite la recursividad a una rutinaLa recursividad cíclica (A llama a B llama a C llama a A) es


peligrosa porque es difícil de detectar. Manejar mentalmente la recursividad en una rutina es
bastante difícil; comprender la recursividad que abarca rutinas es demasiado. Si tiene una recursión
cíclica, normalmente puede rediseñar las rutinas para que la recursión se restrinja a un
17.2 Recursividad 397

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

En general, la experiencia en las dos décadas que siguieron a la publicación de la carta de


Dijkstra mostró la locura de producirir-código cargado. En un estudio de la literatura, Ben
Shneiderman concluyó que la evidencia respalda la opinión de Dijkstra de que estamos mejor
sin lair(1980), y muchos lenguajes modernos, incluido Java, ni siquiera tienenirs.

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.

un bien colocadoirpuede eliminar la necesidad de código duplicado. El código duplicado genera


problemas si los dos conjuntos de código se modifican de manera diferente. El código duplicado
aumenta el tamaño de los archivos fuente y ejecutables. Los malos efectos de lairson superados en
tal caso por los riesgos de código duplicado.

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

errores yirs” en esta sección. Consulte


losirreduce la probabilidad de que se olvide de desasignar los recursos en cada
también la discusión sobre el manejo de lugar donde detecte un error.
excepciones en la Sección 8.4,

“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.

Una buena programación no significa eliminarirs. La descomposición, el refinamiento y la


selección metódicos de estructuras de control conducen automáticamente air-programas
gratuitos en la mayoría de los casos. Lograndoir-less código no es el objetivo sino el resultado,
y poner el foco en evitarirs no es útil.

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 desempeño [del programador].


resultados no concluyentes no respaldaron la afirmación de Shneiderman y otros de que la cantidad
Estos experimentos de errores en el código era proporcional a la cantidad de errores.ir(1981). Sheil no fue tan lejos como
prácticamente no proporcionan
para concluir que usarirs es una buena idea, más bien, que la evidencia experimental en su contra no
evidencia del efecto beneficioso

de ningún método específico para


fue concluyente.
estructurar el flujo de control.
—BA Sheil Finalmente, elirse ha incorporado a muchos lenguajes modernos, incluidos Visual Basic, C++ y el
lenguaje Ada, el lenguaje de programación más cuidadosamente diseñado de la historia. Ada se
desarrolló mucho después de los argumentos de ambos lados de lair El debate se había desarrollado
completamente, y después de considerar todos los lados del problema, los ingenieros de Ada
decidieron incluir elir.
400 Capítulo 17: Estructuras de control inusuales

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:

Ejemplo en C++ de código que se supone que es fácil de reescribir sinirs


hacer {
GetData (archivo de entrada, datos);
if (eof(archivo de entrada)) {
ir a LOOP_EXIT;
}
HacerAlgo(datos);
} mientras (datos! = -1);
LOOP_SALIDA:

El libro reemplaza rápidamente este código conir-menos código:

Ejemplo en C++ de código supuestamente equivalente, reescrito sinirs


GetData (archivo de entrada, datos);
while ( ( !eof( archivo de entrada ) ) && ( ( datos != -1 ) ) ) {
HacerAlgo(datos);
GetData (archivo de entrada, datos)
}

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.

Aquí hay una traducción fiel del código sinirs:


17..3 ir a 401

Ejemplo en C++ de código verdaderamente equivalente, reescrito sinirs


hacer {
GetData (archivo de entrada, datos);
if (! eof (archivo de entrada)) {
HacerAlgo(datos);
}
} while ( ( datos != -1 ) && ( !eof( archivoEntrada ) ) );

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.

Procesamiento de errores yirs


Escribir código altamente interactivo requiere prestar mucha atención al procesamiento de errores y
limpiar los recursos cuando ocurren errores. El siguiente ejemplo de código purga un grupo de
archivos. La rutina primero obtiene un grupo de archivos para purgar y luego encuentra cada
archivo, lo abre, lo sobrescribe y lo borra. La rutina comprueba si hay errores en cada paso.

Código de Visual Basic conirs que procesa errores y limpia recursos


' 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)

estado de error = FileStatus_Success


índice de archivo =0
Mientras que (fileIndex < numFilesToPurge )
índicearchivo = índicearchivo + 1
Si no (FindFile(fileList(fileIndex), fileToPurge)) Entonces
errorState = FileStatus_FileFindError Ir a END_PROC
Aquí está unIr.
Terminara si
402 Capítulo 17: Estructuras de control inusuales

Si no es OpenFile (fileToPurge), entonces


errorState = FileStatus_FileOpenError Ir a
Aquí está unIr. END_PROC
Terminara si

Si no se sobrescribe el archivo (fileToPurge), entonces


errorState = FileStatus_FileOverwriteError Ir a END_PROC
Aquí está unIr.
Terminara si

si no se borra (fileToPurge) Entonces


errorState = FileStatus_FileEraseError Ir a END_PROC
Aquí está unIr.
Terminara si

Final Tiempo

Aquí esta laIretiqueta. END_PROC:


DeletePurgeFileList(fileList, numFilesToPurge) End Sub

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:

Reescribir con anidadosideclaracionesPara reescribir con anidadosideclaraciones, anide elsi


declaraciones para que cada una se ejecute solo si la prueba anterior tiene éxito. Este es el enfoque
estándar de programación de libros de texto para eliminarirs. Aquí hay una reescritura de la rutina
usando el enfoque estándar:

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)

estado de error = FileStatus_Success


índice de archivo =0
losTiempola prueba se ha cambiado Mientras (fileIndex <numFilesToPurge y errorState = FileStatus_Success)
para agregar una prueba para estado

de error. índicearchivo = índicearchivo + 1


17..3 ir a 403

Si FindFile(fileList(fileIndex), fileToPurge) Entonces


Si OpenFile(fileToPurge) Entonces
Si OverwriteFile(fileToPurge) Entonces
Si no se borra (fileToPurge), entonces
errorState = FileStatus_FileEraseError Terminar si

Else ' no pudo sobrescribir el archivo


errorState = FileStatus_FileOverwriteError Finalizar si

Else ' no pudo abrir el archivo


errorState = FileStatus_FileOpenError Terminar si

Else ' no pudo encontrar el archivo


Esta línea está a 13 líneas de errorState = FileStatus_FileFindError Terminar si
distancia de laSisentencia que

lo invoca. Final Tiempo


DeletePurgeFileList(fileList, numFilesToPurge)
Final Sub

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,

consulte el Capítulo 31, "Diseño y estilo".


la vez. Además, la distancia entre el código de procesamiento de errores y el código que lo invoca es
Para obtener detalles sobre los niveles de demasiado grande: el código que estableceestado de erroraFileStatus_FileFindError, por ejemplo,
anidamiento, consulte la Sección 19.4,
está a 13 líneas delsisentencia que lo invoca.
“Domar el anidamiento peligrosamente

Con elirversión, ninguna declaración está a más de cuatro líneas de la condición que la
profundo”.

invoca. Y no tienes que mantener toda la estructura en tu mente a la vez. Básicamente,


puede ignorar cualquier condición anterior que haya tenido éxito y centrarse en la
siguiente operación. En este caso, elirLa versión es más legible y más fácil de mantener
que la anidada.siversión.

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)

estado de error = FileStatus_Success


índice de archivo =0
404 Capítulo 17: Estructuras de control inusuales

losTiempola prueba se ha cambiado Mientras que (fileIndex <numFilesToPurge) y (errorState = FileStatus_Success)


para agregar una prueba para estado

de error. índicearchivo = índicearchivo + 1

Si no es FindFile (fileList (fileIndex), fileToPurge), entonces


errorState = FileStatus_FileFindError Terminar si

La variable de estado se prueba. Si ( estado de error = FileStatus_Success ) Entonces


Si no es OpenFile (fileToPurge), entonces
errorState = FileStatus_FileOpenError Terminar si

Terminara si

La variable de estado se prueba. Si ( estado de error = FileStatus_Success ) Entonces


Si no se sobrescribe el archivo (fileToPurge), entonces
errorState = FileStatus_FileOverwriteError Finalizar si

Terminara si

La variable de estado se prueba. Si ( estado de error = FileStatus_Success ) Entonces


Si no se borra (fileToPurge), entonces
errorState = FileStatus_FileEraseError Terminar si

Terminara si

Final Tiempo
DeletePurgeFileList(fileList, numFilesToPurge)
Final Sub

La ventaja del enfoque de variable de estado es que evita el anidamiento profundoifthen-else


estructuras de la primera reescritura y, por lo tanto, es más fácil de entender. También sitúa la
acción a continuación de lasi-entonces-otroprueba más cerca de la prueba que el anidado-sienfoque
lo hizo, y evita por completomáscláusulas.

Comprender el anidado-siversión requiere un poco de gimnasia mental. La versión de


variable de estado es más fácil de entender porque modela de cerca la forma en que las
personas piensan sobre el problema. Encuentras el archivo. Si todo está bien, abres el
archivo. Si todo sigue bien, sobrescribe el archivo. Si todo sigue bien...

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.

reescribir conintentar-finalmenteAlgunos lenguajes, incluidos Visual Basic y Java, proporcionan una


intentar-finalmenteinstrucción que se puede utilizar para limpiar recursos en condiciones de error.

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

Código de Visual Basic que evitairs usandointentar-finalmente


' Esta rutina purga un grupo de archivos. Las excepciones se pasan a la persona que llama. Sub
Purgar archivos ()
Dim fileIndex As Integer Dim fileToPurge As Data_File Dim
fileList As File_List Dim numFilesToPurge As Integer
MakePurgeFileList(fileList, numFilesToPurge) Try

í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>

La ventaja de laintentar-finalmenteenfoque es que es más simple que elirenfoque y no utiliza


ve a S. También evita los anidados profundossi-entonces-otroestructuras

La limitación de laintentar-finalmenteEl enfoque es que debe implementarse de manera consistente


a lo largo de una base de código. Si el código anterior fuera parte de una base de código que usaba
códigos de error además de excepciones, se requeriría que el código de excepción estableciera
códigos de error para cada posible error, y ese requisito haría que el código fuera tan complicado
como los otros enfoques.

Comparación de los enfoques


Referencia cruzadaPara obtener una Cada uno de los cuatro métodos tiene algo que decir. losirEl enfoque evita el anidamiento profundo y
lista completa de técnicas que se
las pruebas innecesarias, pero por supuesto tieneirs. el anidado-siel enfoque evitairs pero está
pueden aplicar a situaciones como

esta, consulte "Resumen de técnicas


profundamente anidado y da una imagen exagerada de la complejidad lógica de la rutina. El enfoque
para reducir el anidamiento de la variable de estado evitairs y anidamiento profundo, pero introduce pruebas adicionales. Y el
profundo" en la Sección 19.4. intentar-finalmenteenfoque evita ambosirs y anidamiento profundo, pero no está disponible en
todos los idiomas.

losintentar-finalmenteenfoque es el más sencillo en lenguajes que proporcionanintenta


finalmentey en bases de código que aún no se han estandarizado en otro enfoque. Si intentar-
finalmenteno es una opción, el enfoque de variable de estado es ligeramente preferible alir y
anidado-sienfoques porque es más legible y modela mejor el problema, pero eso no lo
convierte en el mejor enfoque en todas las circunstancias.

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

irs y código compartido en unmásCláusula


Una situación desafiante en la que algunos programadores usarían unires el caso en el
que tienes dos pruebas condicionales y unamáscláusula y desea ejecutar código en una
de las condiciones y en elmáscláusula. He aquí un ejemplo de un caso que podría llevar a
alguien air:

Ejemplo en C++ de código compartido en unmásCláusula con unir


si (estadoOk) {
si (datos disponibles) {
CODIFICACIÓN importanteVariable = x;
HORROR
ir a MID_LOOP;
}
}
más {
variable importante = ObtenerValor();

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:

Ejemplo en C++ de código compartido en unmásCláusula poniendo código común en


una rutina
si (estadoOk) {
si (datos disponibles) {
importanteVariable = x;
DoLotsOfCode( variable importante);
}
}
más {
importanteVariable = ObtenerValor();
DoLotsOfCode(variableimportante );
}
17..3 ir a 407

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:

Ejemplo en C++ de código compartido en unmásCláusula sinir


if (( estadoOk && datosDisponibles ) || !statusOk ) {
if (estadoOk && datos disponibles) {
importanteVariable = X;
}
más {
importanteVariable = ObtenerValor();
}

// 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

información, consulte el Capítulo 18,


condicionales, observa que el valor deestadoOKno necesita ser probado dos veces en el primerosi
“Métodos controlados por tablas”. prueba. También puede abandonar la prueba paradatos disponiblesen el segundosiprueba.

Resumen de las pautas de usoirs


Uso deirs es una cuestión de religión. Mi dogma es que en los idiomas modernos, puedes reemplazar
fácilmente nueve de cada diezirs con construcciones secuenciales equivalentes. En estos casos
simples, debe reemplazarires por costumbre. En los casos difíciles, aún puede exorcizar eliren nueve
PUNTO CLAVE
de cada diez casos: puede dividir el código en rutinas más pequeñas, usarintenta finalmente, uso
anidadosis, probar y volver a probar una variable de estado, o reestructurar un condicional.
Eliminando elires más difícil en estos casos, pero es un buen ejercicio mental y las técnicas discutidas
en esta sección le brindan las herramientas para hacerlo.

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.

Aquí hay un resumen de las pautas para usarirs:

- Usarirs para emular construcciones de control estructurado en lenguajes que no las


admiten directamente. Cuando lo haga, imítelos exactamente. No abuse de la
flexibilidad adicional queirte dio.

- no use elircuando una construcción integrada equivalente está disponible.


Referencia cruzadaPara obtener detalles - Medir el rendimiento de cualquierirutilizado para mejorar la eficiencia. En la mayoría de
los casos, puede recodificar sinirs para una mejor legibilidad y sin pérdida de eficiencia.
sobre cómo mejorar la eficiencia, consulte

el Capítulo 25, "Estrategias de ajuste de

código" y el Capítulo 26, "Técnicas de


Si su caso es la excepción, documente la mejora de la eficiencia para queir- menos
ajuste de código". evangelistas no quitarán elircuando lo ven.
408 Capítulo 17: Estructuras de control inusuales

- limítate a unoiretiqueta por rutina a menos que esté emulando construcciones


estructuradas.

- limítate airs que avanzan, no retroceden, a menos que esté emulando


construcciones estructuradas.
- Asegúrate de que todoirse utilizan etiquetas. Las etiquetas no utilizadas pueden ser una indicación de que

falta un código, es decir, el código que va a las etiquetas. Si las etiquetas no se utilizan, elimínelas.

- Asegúrese de que unirno crea código inalcanzable.


- Si es gerente, adopte la perspectiva de que una batalla por un soloirno vale la pena
perder la guerra. Si el programador es consciente de las alternativas y está dispuesto a
discutir, elirprobablemente esté bien.

17.4 Perspectiva sobre estructuras de control inusuales


En un momento u otro, alguien pensó que cada una de las siguientes estructuras de control era una
buena idea:

- Uso sin restricciones deirs

- Habilidad para calcular unirapuntar dinámicamente y saltar a la ubicación calculada

- capacidad de usoirsaltar del medio de una rutina al medio de otra rutina

- 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.

cc2e.com/1799 Dijkstra, Edsger. "Ir a la declaración considerada dañina".Comunicaciones de la ACM


11, núm. 3 (marzo de 1968): 147–48, también disponible enwww.cs.utexas.edu/users/EWD/.
Esta es la famosa carta en la que Dijkstra puso el fósforo en el papel y encendió una de las
controversias más antiguas en el desarrollo de software.

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.

Knuth, Donald. “Programación estructurada con sentencias go to”, 1974. EnClásicos en


Ingeniería de Software, editado por Edward Yourdon. Englewood Cliffs, NJ: Yourdon
Press, 1979. Este largo artículo no trata del todoirs, pero incluye una horda de ejemplos
de código que se vuelven más eficientes al eliminarirs y otra horda de ejemplos de código
que se vuelven más eficientes al agregarirs.

Rubin, Frank. "'GOTO Considerado Dañino' Considerado Dañino".Comunicaciones de la ACM30,


núm. 3 (marzo de 1987): 195–96. En esta carta bastante impetuosa al editor, Rubin afirma queir
-La programación sin costo le ha costado a las empresas "cientos de millones de dólares".
Luego ofrece un fragmento de código corto que usa uniry argumenta que es superior air
-Menos alternativas.

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.

Para las cartas de seguimiento, véase

- Comunicaciones de la ACM30, núm. 5 (mayo de 1987): 351–55.

- Comunicaciones de la ACM30, núm. 6 (junio de 1987): 475–78.

- Comunicaciones de la ACM30, núm. 7 (julio de 1987): 632–34.


410 Capítulo 17: Estructuras de control inusuales

- Comunicaciones de la ACM30, núm. 8 (agosto de 1987): 659–62.

- Comunicaciones de la ACM30, núm. 12 (diciembre de 1987): 997, 1085.

cc2e.com/1706 Clark, R. Lawrence, "Una contribución lingüística de la programación sin GOTO",Datamación,


diciembre de 1973. Este artículo clásico argumenta con humor a favor de reemplazar la
declaración "ir a" con la declaración "venir de". También se reimprimió en la edición de abril de
1974 deComunicaciones de la ACM.

cc2e.com/1713 LISTA DE VERIFICACIÓN: Estructuras de control inusuales

devolver

- ¿Cada rutina usadevolver¿Solo cuando sea necesario?

- Hacerdevolvers mejorar la legibilidad?

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?

- ¿La recursividad está limitada a una rutina?

- ¿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?

- si unirse utiliza en aras de la eficiencia, ¿se ha medido y documentado el


aumento de la eficiencia?

- Sonir¿Está limitado a una etiqueta por rutina?

- Haz todoirs ir hacia adelante, no hacia atrás?

- Son todosiretiquetas usadas?

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

Métodos controlados por tablas

cc2e.com/1865 Contenido

- 18.1 Consideraciones generales sobre el uso de métodos controlados por tablas: página 411

- 18.2 Tablas de acceso directo: página 413

- 18.3 Tablas de acceso indexado: página 425

- 18.4 Mesas de acceso a peldaños: página 426

- 18.5 Otros ejemplos de búsquedas en tablas: página 429

Temas relacionados

- Ocultación de información: “Ocultar secretos (Ocultar información)” en la Sección 5.3

- Diseño de clases: Capítulo 6

- Uso de tablas de decisión para reemplazar la lógica complicada: en la Sección 19.1

- Búsquedas en tablas de sustitución para expresiones complicadas: en la Sección 26.1

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.

18.1 Consideraciones generales sobre el uso de métodos controlados por tablas

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

Ejemplo de Java del uso de lógica complicada para clasificar un personaje


if ( ( ( 'a' <= inputChar ) && ( inputChar <= 'z' ) ) ||
( ( 'A' <= inputChar ) && ( inputChar <= 'Z' ) ) ) { charType =
CharacterType.Letter;
}
de lo contrario si ((inputChar == ' ' ) || ( inputChar == ',' ) ||
( carácter de entrada == '.' ) || ( carácter de entrada == '!' ) || ( carácter de entrada == '(' ) ||
(carácter de entrada == ')' ) || ( carácter de entrada == ':' ) || (CarácterEntrada == ';' ) ||
(CarácterEntrada == '?' ) || ( carácter de entrada == '-' ) ) {
charType = CharacterType.Punctuation;
}
de lo contrario si (('0' <= carácter de entrada) && (carácter de entrada <= '9')) {
charType = CharacterType.Digit;
}

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

Dos problemas en el uso de métodos controlados por tablas

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

- Acceso por escalera

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.

18.2 Tablas de acceso directo


Como todas las tablas de búsqueda, las tablas de acceso directo reemplazan estructuras de control
lógico más complicadas. Son de "acceso directo" porque no tiene que pasar por ningún aro
complicado para encontrar la información que desea en la tabla. Como sugiere la Figura 18-1, puede
seleccionar la entrada que desee directamente.

(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 días en el mes


Suponga que necesita determinar la cantidad de días por mes (olvidando el año bisiesto, por el
bien del argumento). Una forma torpe de hacerlo, por supuesto, es escribir un gransi
declaración:

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

414 Capítulo 18: Métodos controlados por tablas

ElseIf ( mes = 8 ) Entonces


días = 31
ElseIf ( mes = 9 ) Entonces
días = 30
ElseIf (mes = 10) Entonces
días = 31
ElseIf ( mes = 11 ) Entonces
días = 30
ElseIf (mes = 12) Entonces
días = 31
Terminara si

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:

Ejemplo de Visual Basic de una Manera Elegante de Determinar el Número de Días en un


Mes
' Inicializa la tabla de datos "Días por mes" Dim
daysPerMonth() As Integer = _
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }

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:

Ejemplo de Visual Basic de una forma elegante de determinar la cantidad de días en un


mes (continuación)
días = días por mes (mes-1)

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:

Ejemplo de Visual Basic de una forma elegante de determinar la cantidad de días en un


mes (continuación)
días = días por mes (mes-1, índice de año bisiesto ())

En elsi-versión de declaración, la cadena larga desis se volvería aún más complicado si se


considerara el año bisiesto.

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

Ejemplo de tarifas de seguro


Suponga que está escribiendo un programa para calcular las tarifas de seguro médico y tiene
tarifas que varían según la edad, el sexo, el estado civil y si una persona fuma. Si tuviera que
escribir una estructura de control lógica para las tasas, obtendría algo como esto:

Ejemplo de Java de una forma torpe de determinar una tasa de seguro


if (género == Género.Mujer) {
if ( estado civil == estado civil. Soltero ) {
CODIFICACIÓN if (EstadoFumador == EstadoFumador.NoFumador) {
HORROR
si ( edad < 18 ) {
tasa = 200,00;
}
más si ( edad == 18 ) {
tasa = 250,00;
}
si no ( edad == 19 ) {
tasa = 300,00;
}
...
si no ( 65 < edad ) {
tasa = 450,00;
}
más {
si ( edad < 18 ) {
tasa = 250,00;
}
más si ( edad == 18 ) {
tasa = 300,00;
}
si no ( edad == 19 ) {
tasa = 350,00;
}
...
si no ( 65 < edad ) {
tasa = 575,00;
}
}
else if ( estado civil == estado civil. Casado ) . . .

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

Género de enumeración pública


género_primero = 0
Género masculino =0
Género femenino = 1
Género_Apellido = 1
Enumeración final

Enumeración pública Estado civil


Estado civil_primero = 0
Estado civil soltero = 0
Estado Civil: Casado = 1
Estado civil_último = 1
Enumeración final

Const MAX_AGE como entero = 125

Dim rateTable ( SmokingStatus_Last, Gender_Last, MaritalStatus_Last, _


MAX_AGE ) como doble

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

de la tabla en un archivo y leerlos en


sea apropiado. Una vez que haya configurado los datos, los tendrá listos cuando necesite calcular
tiempo de ejecución. Eso le permite una tasa. La lógica complicada que se mostró anteriormente se reemplaza con una declaración
cambiar algo como una tabla de
simple como esta:
tarifas de seguros sin cambiar el

programa en sí. Para obtener más

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.

Ejemplo de formato de mensaje flexible


Puede usar una tabla para describir la lógica que es demasiado dinámica para representarla en el
código. Con el ejemplo de clasificación de caracteres, el ejemplo de días en el mes y el ejemplo de
tarifas de seguro, al menos sabía que podía escribir una larga cadena desideclaraciones si
18.2 Tablas de acceso directo 417

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

Contenido del mensaje

ID para boya
Mensaje de deriva

Contenido del mensaje

ID para boya
Mensaje de ubicación

Contenido del mensaje

Figura 18-2 Los mensajes se almacenan sin ningún orden en particular y cada uno se identifica con un
identificador de mensaje

El formato de los mensajes es volátil, determinado por su cliente, y no tiene suficiente


control sobre su cliente para estabilizarlo. La figura 18-3 muestra cómo se ven algunos
de los mensajes en detalle.
418 Capítulo 18: Métodos controlados por tablas

ID para boya ID para boya ID para boya


Mensaje de temperatura Mensaje de deriva Mensaje de ubicación

Temperatura media Cambio en la latitud Latitud


(punto flotante) (punto flotante) (punto flotante)

Rango de temperatura Cambio de longitud Longitud


(punto flotante) (punto flotante) (punto flotante)

Número de muestras Tiempo de medición Profundidad

(entero) (hora del día) (entero)

Ubicación Tiempo de medición


(cadena de caracteres) (hora del día)

Tiempo de medición
(hora del día)

Figura 18-3Además del ID del mensaje, cada tipo de mensaje tiene su propio formato.

Enfoque basado en la lógica

Si utilizó un enfoque basado en la lógica, probablemente leería cada mensaje, verificaría la ID y


luego llamaría a una rutina diseñada para leer, interpretar e imprimir cada tipo de mensaje. Si
tuviera 20 tipos de mensajes, tendría 20 rutinas. También tendría quién sabe cuántas rutinas
de nivel inferior para respaldarlas; por ejemplo, tendría un
ImprimirBoyaTemperatureMessage()rutina para imprimir el mensaje de temperatura de la
boya. Un enfoque orientado a objetos no sería mucho mejor: normalmente usaría un objeto
de mensaje abstracto con una subclase para cada tipo de mensaje.

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:

Referencia cruzadaEste Mientras más mensajes para leer


pseudocódigo de bajo nivel se usa Leer un encabezado de mensaje
para un propósito diferente al del Descodifique el ID del mensaje del encabezado del mensaje. Si el
pseudocódigo que usa para el diseño encabezado del mensaje es de tipo 1, entonces
de rutinas. Para obtener detalles Imprimir un mensaje de tipo 1
sobre el diseño en pseudocódigo, De lo contrario, si el encabezado del mensaje es de tipo 2, entonces

consulte el Capítulo 9, “El Imprimir un mensaje de tipo 2


Programación de pseudocódigo ...
Proceso." De lo contrario, si el encabezado del mensaje es del tipo 19, entonces

Imprimir un mensaje de tipo 19


De lo contrario, si el encabezado del mensaje es del tipo 20, entonces

Imprimir un mensaje de tipo 20


18.2 Tablas de acceso directo 419

El pseudocódigo está abreviado porque puede hacerse una idea sin ver los 20 casos.

Enfoque orientado a objetos

Si estuviera utilizando un enfoque orientado a objetos de memoria, la lógica estaría oculta en la


estructura de herencia de objetos, pero la estructura básica sería igual de complicada:

Mientras más mensajes para leer


Leer un encabezado de mensaje
Descodifique el ID del mensaje del encabezado del mensaje. Si el
encabezado del mensaje es de tipo 1, entonces
Crear una instancia de un objeto de mensaje de tipo 1 De lo
contrario, si el encabezado del mensaje es de tipo 2, entonces
Crear una instancia de un objeto de mensaje de tipo 2
...
De lo contrario, si el encabezado del mensaje es del tipo 19, entonces
Crear una instancia de un objeto de mensaje de tipo 19 De lo
contrario, si el encabezado del mensaje es de tipo 20, entonces
Instanciar un objeto de mensaje de tipo 20
Terminar si
Terminar mientras

Independientemente de si la lógica está escrita directamente o contenida en clases


especializadas, cada uno de los 20 tipos de mensajes tendrá su propia rutina para imprimir su
mensaje. Cada rutina también podría expresarse en pseudocódigo. Este es el pseudocódigo de
la rutina para leer e imprimir el mensaje de temperatura de la boya:

Imprimir "Mensaje de temperatura de la boya"

Leer un valor de punto flotante Imprimir


"Temperatura promedio" Imprimir el
valor de punto flotante

Leer un valor de punto flotante Imprimir


"Rango de temperatura" Imprimir el valor
de punto flotante

Leer un valor entero Imprimir


"Número de muestras" Imprimir el
valor entero

Leer una cadena de caracteres


Imprimir "Ubicación"
Imprimir la cadena de caracteres

Leer una hora del día Imprimir "Hora


de medición" Imprimir la hora del día

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

Enfoque basado en 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:

Ejemplo de C++ de definición de tipos de datos de mensajes


enumerar tipo de campo {
FieldType_FloatingPoint,
FieldType_Integer,
Tipo de campo_Cadena,
FieldType_TimeOfDay,
tipo de campo_booleano,
Tipo de campo_BitField,
FieldType_Last = FieldType_BitField
};

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í:

Ejemplo de definición de una entrada en la tabla de mensajes

Comienzo del mensaje

Campos numéricos 5

MessageName "Mensaje de temperatura de la boya" Campo


1, Punto flotante, "Temperatura promedio" Campo 2, Punto
flotante, "Rango de temperatura" Campo 3, Número entero,
"Número de muestras"
Campo 4, Cadena, "Ubicación"
Campo 5, Hora del día, Fin del mensaje "Hora de la
medición"

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

A diferencia del pseudocódigo para el enfoque basado en la lógica, el pseudocódigo en este


caso no se abrevia porque la lógica es mucho menos complicada. En la lógica por debajo de
este nivel, encontrará una rutina que es capaz de interpretar una descripción de mensaje de la
tabla de descripción de mensajes, leer datos de mensajes e imprimir un mensaje. Esa rutina es
más general que cualquiera de las rutinas de impresión de mensajes basadas en lógica, pero
no mucho más complicada, y será una rutina en lugar de 20:

Mientras más campos para imprimir


Obtenga el tipo de campo del caso de descripción del mensaje
(tipo de campo)
de (coma flotante)
leer un valor de punto flotante
imprimir la etiqueta del campo
imprimir el valor de punto flotante

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 (hora del día)


leer una hora del día imprimir
la etiqueta del campo imprimir
la hora del día

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.

Así es como configuraría los tipos de objeto en C++:

Ejemplo de C++ de configuración de tipos de objetos


clase CampoAbstracto {
público:
virtual void ReadAndPrint( cadena, FileStatus & ) = 0;
};

clase FloatingPointField : public AbstractField {


público:
vacío virtual ReadAndPrint (cadena, FileStatus &) {. . .

}
};

clase CampoEntero ...


clase campo de cadena . . .
...

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:

Ejemplo de C++ de configuración de una lista de objetos


campo[ Field_FloatingPoint ] = new FloatingPointField();
campo[ Field_Integer ] = new IntegerField();
campo[ Field_String ] = nuevo StringField();
campo[ Field_TimeOfDay ] = new TimeOfDayField();
campo[ Field_Boolean ] = new BooleanField();
campo[ Field_BitField ] = new BitFieldField();
18.2 Tablas de acceso directo 423

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í:

Ejemplo de C++ de búsqueda de objetos y rutinas de miembros en una tabla


Este material es solo campoIdx = 1;
limpieza para cada campo while ((fieldIdx <= numFieldsInMessage) && (fileStatus == OK)) {
en un mensaje. fieldType = fieldDescription[ fieldIdx ].FieldType; fieldName =
fieldDescription[ fieldIdx ].FieldName; campo[ tipo de
Esta es la búsqueda de tabla que campo ].ReadAndPrint( nombre de campo, estado de archivo ); campoIdx++;
llama a una rutina dependiendo

del tipo de campo simplemente }


buscándolo en una tabla de

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.

Falsificar claves de búsqueda

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

max(min(66, edad), 17)

para crear una clave de tabla que oscile entre 17 y 66.

La creación de la función de transformación requiere que reconozca un patrón en los


datos que desea usar como clave, y eso no siempre es tan simple como usar elmin()y
máx()rutinas Suponga que en este ejemplo las tasas fueran para franjas de edad de cinco
años en lugar de franjas de un año. A menos que quisiera duplicar todos sus datos cinco
veces, tendría que idear una función que dividieraAñospor 5 correctamente y usó el min()
ymáx()rutinas

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

18.3 Tablas de acceso indexado


A veces, una simple transformación matemática no es lo suficientemente potente como para dar el salto a partir de
datos comoAñosa una clave de tabla. Algunos de estos casos son adecuados para el uso de un esquema de acceso
indexado.

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.

18.4 Mesas de acceso para escalones


Otro tipo de acceso a la mesa es el método de escalones. Este método de acceso no es tan directo
como una estructura de índice, pero no desperdicia tanto espacio de datos.

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:

Ejemplo de Visual Basic de una tabla de búsqueda de escalones


' configurar datos para la tabla de calificación
Dim rangeLimit() As Double = { 50.0, 65.0, 75.0, 90.0, 100.0 } Dim grade() As String =
{ "F", "D", "C", "B", "A" }
maxGradeLevel = grado.Longitud – 1 . . .

' asignar una calificación a un estudiante basado en el puntaje del estudiante


gradeLevel = 0
estudianteGrado = "A"
Mientras que ((estudianteCalificación = "A" ) y (gradoNivel < maxGradeLevel ) )
Si (puntuación del estudiante < rangeLimit( gradeLevel ) ) Entonces
estudianteGrado = grado( gradoNivel ) End If

gradoNivel = gradoNivel + 1 Wend

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:

Probabilidad Monto de reclamo de seguro

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

Probabilidad Monto de reclamo de seguro

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.

Aquí hay algunas sutilezas a considerar al usar la técnica del escalón:

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

datos de probabilidad enumerados anteriormente, no podría configurar un esquema de indexación


porque no puede ingresar entradas con números como 0.458747 y 0.547651.

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

Capítulo 5, "Diseño en la construcción".


mejor. Como dice Butler Lampson, un distinguido ingeniero de Microsoft, es mejor luchar por
una buena solución y evitar el desastre en lugar de tratar de encontrar la mejor solución
(Lampson 1984).

Ponga la búsqueda de la tabla de escalones en su propia rutinaCuando crea una función de


transformación que cambia un valor comoEstudianteGradoen una clave de tabla, ponerlo en su
propia rutina.

18.5 Otros ejemplos de búsquedas en tablas


Algunos otros ejemplos de búsquedas en tablas aparecen en otras secciones del libro. Se usan
en el curso de la discusión de otras técnicas, y los contextos no enfatizan las búsquedas en la
tabla per se. Aquí es donde los encontrará:

- 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”

- Combinaciones de valores booleanos (A o B o C): "Búsquedas de tablas sustitutas para


expresiones complicadas" en la Sección 26.1

- Precálculo de valores en una tabla de reembolso de préstamos: Sección 26.4, “Expresiones”.

cc2e.com/1872 LISTA DE VERIFICACIÓN: Métodos controlados por tablas

- ¿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?

- ¿Ha considerado almacenar los datos de la tabla externamente y leerlos en tiempo de


ejecución para que los datos puedan modificarse sin cambiar el código?

- Si no se puede acceder a la tabla directamente a través de un índice de matriz


directo (como en elañosejemplo), ¿ha incluido el cálculo de la clave de acceso en
una rutina en lugar de duplicar el cálculo del índice en el código?
430 Capítulo 18: Métodos controlados por tablas

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

Problemas generales de control

cc2e.com/1978 Contenido

- 19.1 Expresiones booleanas: página 431

- 19.2 Sentencias compuestas (Bloques): página 443

- 19.3 Sentencias nulas: página 444

- 19.4 Domar anidamientos peligrosamente profundos: página 445

- 19.5 Una base de programación: Programación estructurada: página 454

- 19.6 Estructuras de control y complejidad: página 456

Temas relacionados

- Código de línea recta: Capítulo 14

- Código con condicionales: Capítulo 15

- Código con bucles: Capítulo 16

- Estructuras de control inusuales: Capítulo 17

- Complejidad en el desarrollo de software: “Imperativo técnico primario del software: gestión


de la complejidad” en la Sección 5.2

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.

19.1 Expresiones booleanas


Excepto por la estructura de control más simple, la que exige la ejecución de sentencias en
secuencia, todas las estructuras de control dependen de la evaluación de expresiones booleanas.

Usandoverdaderoyfalsopara pruebas booleanas

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 PrinterError = 0 Entonces InitializePrinter() Si PrinterError =


1 Entonces NotifyUserOfError()

Si informeSeleccionado = 1 Entonces ImprimirReporte() Si


resumenSeleccionado = 1 Entonces ImprimirResumen()

Si PrinterError = 0 Entonces CleanupPrinter()

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.

Usar términos nombradosverdaderoyfalsopara pruebas con expresiones booleanas. Si su idioma no admite


dichos términos directamente, créelos utilizando macros de preprocesador o variables globales. El ejemplo
de código anterior se reescribe aquí usando el código integrado de Microsoft Visual Basic.VerdaderoyFalso:

Bueno, pero no excelente Ejemplos de uso de Visual BasicVerdaderoyFalsopara pruebas en lugar de


valores numéricos
Referencia cruzadaPor un Dim printerError As Boolean Dim
enfoque aún mejor para hacer estas reportSelected As ReportType Dim
mismas pruebas, consulte el summarySelected As Boolean . . .
siguiente ejemplo de código.

If (printError = False) Entonces InitializePrinter() If (printerError = True)


Then NotifyUserOfError()

If (reportSelected = ReportType_First) Entonces PrintReport() If (summarySelected


= True) Entonces PrintSummary()

Si (printError = False) Entonces CleanupPrinter()

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:

Compara valores booleanos converdaderoyfalsoimplícitamentePuede escribir pruebas más claras


tratando las expresiones como expresiones booleanas. Por ejemplo, escribe

while ( no hecho ) ... while ( a >


b ) ...

más bien que

while ( hecho = falso ) ... while ( (a > b) =


verdadero ) ...

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:

Mejores ejemplos de Visual Basic de pruebas paraVerdaderoyFalsoImplícitamente


Dim printerError As Boolean Dim
reportSelected As ReportType Dim
summarySelected As Boolean . . .

If (Not printerError) Entonces InitializePrinter() If (printError) Then


NotifyUserOfError()

Si (informeSeleccionado = TipoDeInforme_Primero) Entonces ImprimirInforme() Si


(resumenSeleccionado) Entonces ImprimirResumen()

Si (No es un error de impresora) Entonces CleanupPrinter()

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).

Hacer que las expresiones complicadas sean simples

Puede seguir varios pasos para simplificar expresiones complicadas:

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

Mover expresiones complicadas a funciones booleanasSi una prueba se repite con


frecuencia o distrae del flujo principal del programa, mueva el código de la prueba a una
función y pruebe el valor de la función. Por ejemplo, aquí hay una prueba complicada:

Ejemplo de Visual Basic de una prueba complicada


Si ((document.AtEndOfStream) y (no error de entrada)) y _
((MIN_LINES <= lineCount) And (lineCount <= MAX_LINES)) And _ (Not ErrorProcessing())
Entonces
' hacer una cosa u otra . . .

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

Dim allDataRead como booleano Dim


legalLineCount 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

El nuevo nombre de la función introduce una abstracción en el programa que documenta el


propósito de la prueba.en codigo. Eso es incluso mejor que documentar la prueba con comentarios
porque es más probable que se lea el código que los comentarios y también es más probable que se
mantenga actualizado.

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

18, "Métodos controlados por tablas".


prueba en lugar de usarsis ocasos. Una búsqueda de tabla de decisiones es más fácil de codificar
inicialmente, ya que tiene solo un par de líneas de código y no tiene estructuras de control
complicadas. Esta minimización de la complejidad minimiza la oportunidad de cometer errores. Si sus
datos cambian, puede cambiar una tabla de decisiones sin cambiar el código; solo necesita actualizar
el contenido de la estructura de datos.

Formar Expresiones Booleanas Positivamente


No soy un tonto. No pocas personas no tienen ningún problema para comprender una serie no corta de no
—Homero Simpson
positivos, es decir, la mayoría de las personas tienen problemas para comprender muchos
negativos. Puede hacer varias cosas para evitar complicadas expresiones booleanas negativas en
sus programas:

Ensideclaraciones, convertir negativos en positivos y voltear el código en elsiymás cláusulas


Aquí hay un ejemplo de una prueba expresada negativamente:

Ejemplo de Java de una prueba booleana negativa confusa


aqui esta lo negativono. si (!estadoOK) {
// hacer algo
...
}
más {
// hacer algo más . . .

Puede cambiar esto a la siguiente prueba expresada positivamente:

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:

Ejemplo de Java de una prueba negativa


if ( !displayOK || !printerOK ) ...

Esto es lógicamente equivalente a lo siguiente:

Ejemplo de Java después de aplicar los teoremas de DeMorgan


if ( !( mostrarOK && impresoraOK ) ) ...

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.

Tabla 19-1 Transformaciones de expresiones lógicas según los teoremas de DeMorgan

Expresión inicial Expresión equivalente


no A y no B no (A o B)
no A y B no ( A o no B ) no
A y no B ( no A o B ) no ( no A
AyB o no B ) no ( A y B )
no A o no B*
ni A ni B no (A y no B) no (no A
A o no B y B) no (no A y no B)
AoB
* Esta es la expresión utilizada en el ejemplo.
19.1 Expresiones booleanas 437

Uso de paréntesis para aclarar expresiones booleanas


Referencia cruzadaPor un Si tiene una expresión booleana complicada, en lugar de confiar en el orden de
ejemplo de uso de paréntesis
evaluación del idioma, coloque un paréntesis para aclarar su significado. El uso de
para aclarar otros tipos de

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.

Aquí hay una expresión con muy pocos paréntesis:

Ejemplo Java de una expresión que contiene muy pocos paréntesis


si ( un < segundo == c == d ) ...

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:

Ejemplo Java de una expresión mejor entre paréntesis


si ( ( un < segundo ) == ( c == re ) ) ...

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

paréntesis, corchetes y llaves. Para


diciendo "cero". Muévase a lo largo de la expresión, de izquierda a derecha. Cuando encuentre un
obtener detalles sobre los editores de paréntesis de apertura, diga "uno". Cada vez que encuentre otro paréntesis de apertura, aumente el
programación, consulte "Edición" en
número que dice. Cada vez que encuentre un paréntesis de cierre, disminuya el número que dice. Si,
la Sección 30.2.
al final de la expresión, regresas a 0, tus paréntesis están equilibrados.

Ejemplo de Java de paréntesis equilibrados


Lee esto. if ( ( ( a < b ) == ( c == d ) ) && !hecho ) ...
| | | | | | | |
Di esto. 0 1 2 3 2 3 2 1 0
438 Capítulo 19: Problemas generales de control

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:

Ejemplo de Java de paréntesis no balanceados


Lee esto. si ( ( a < b ) == ( c == d ) ) && !hecho ) ...
| | | | | | |
Di esto. 0 1 2 1 2 1 0 -1

El 0 antes de llegar al último paréntesis de cierre es un indicio de que falta un


paréntesis antes de ese punto. No debería obtener un 0 hasta el último paréntesis de
la expresión.

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.

Saber cómo se evalúan las expresiones booleanas


Muchos lenguajes tienen una forma implícita de control que entra en juego en la evaluación de
expresiones booleanas. Los compiladores de algunos lenguajes evalúan cada término en una
expresión booleana antes de combinar los términos y evaluar la expresión completa. Los
compiladores para otros lenguajes tienen una evaluación de "cortocircuito" o "perezosa", evaluando
solo las piezas necesarias. Esto es particularmente significativo cuando, dependiendo de los
resultados de la primera prueba, es posible que no desee que se ejecute la segunda prueba. Por
ejemplo, suponga que está verificando los elementos de una matriz y tiene la siguiente prueba:

Ejemplo de pseudocódigo de una prueba errónea


while ( i < MAX_ELEMENTS y item[ i ] <> 0 ) ...

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.

En pseudocódigo, podría reestructurar la prueba para que no ocurra el error:

Ejemplo de pseudocódigo de una prueba reestructurada correctamente

mientras ( i < MAX_ELEMENTS )


si ( elemento [ i ] <> 0 ) entonces
...

Esto es correcto porqueelemento[ yo ]no se evalúa a menosies menos quemaxElements.


19.1 Expresiones booleanas 439

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

if (AlgoFalso && AlgunaCondición)...

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

si ( algo Verdadero || alguna Condición ) ...

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.

Ejemplo Java de una prueba que funciona debido a la evaluación de cortocircuito


if ( ( denominador != 0 ) && ( ( elemento / denominador ) > MIN_VALUE ) ) ...

Si esta expresión completa fuera evaluada cuandodenominadorigualado0, la división en el segundo


operando produciría un error de división por cero. Pero dado que la segunda parte no se evalúa a menos
que la primera parte sea verdadera, nunca se evalúa cuandodenominadores igual0, por lo que no se
produce ningún error de división por cero.

Por otra parte, porque el&& (y)se evalúa de izquierda a derecha, la siguiente declaración
lógicamente equivalente no funciona:

Ejemplo de Java de una prueba que la evaluación de cortocircuito no rescata


if ( ( ( elemento / denominador ) > MIN_VALUE ) && ( denominador != 0 ) ) ...

En este caso,elemento / denominadorse evalúa antesdenominador != 0. En consecuencia,


este código comete el error de dividir por cero.

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

Pero esto no es seguro:

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.

Escribir expresiones numéricas en orden de recta numérica


Organiza pruebas numéricas para que sigan los puntos en una recta numérica. En general,
estructure sus pruebas numéricas para que tenga comparaciones como estas:

MIN_ELEMENTS <= i y i <= MAX_ELEMENTS i <


MIN_ELEMENTS o MAX_ELEMENTS < i

La idea es ordenar los elementos de izquierda a derecha, de menor a mayor. En la primera


línea, MIN_ELEMENTOSyMAX_ELEMENTOSson los dos extremos, por lo que van en los
extremos. La variableise supone que debe estar entre ellos, por lo que va en el medio. En el
segundo ejemplo, estás probando siiestá fuera del rango, por lo queiva en el exterior de la
prueba en cualquier extremo yMIN_ELEMENTOSyMAX_ELEMENTOSir por dentro. Este enfoque
se asigna fácilmente a una imagen visual de la comparación en la Figura 19-1:

MIN_ELEMENTOS <= i e i <= MAX_ELEMENTOS

MIN_ELEMENTOS Valores válidos para i MAX_ELEMENTOS

i < MIN_ELEMENTOS o MAX_ELEMENTOS < i

MIN_ELEMENTOS MAX_ELEMENTOS

Valores válidos para i

Figura 19-1Ejemplos del uso de la ordenación de líneas numéricas para pruebas booleanas.

Si estás probandoicontraMIN_ELEMENTOSsolamente, la posición deivaría dependiendo de dóndeies


cuando la prueba tiene éxito. Siise supone que es más pequeño, tendrás una prueba como esta:

mientras ( i < MIN_ELEMENTS ) ...

Pero siise supone que es más grande, tendrás una prueba como esta:

mientras (MIN_ELEMENTOS < i ) ...


19.1 Expresiones booleanas 441

Este enfoque es más claro que pruebas como

( i > MIN_ELEMENTOS ) y ( i < MAX_ELEMENTOS )

que no ayudan al lector a visualizar lo que se está probando.

Directrices para las comparaciones con0

uso de lenguajes de programacion0para varios propósitos. Es un valor numérico. Es un


terminador nulo en una cadena. Es el valor de un puntero nulo. Es el valor del primer elemento
de una enumeración. Esfalsoen expresiones lógicas. Debido a que se usa para tantos
propósitos, debe escribir un código que destaque la forma específica0se usa

Comparar variables lógicas implícitamenteComo se mencionó anteriormente, es apropiado


escribir expresiones lógicas como

mientras (!hecho)...

Esta comparación implícita con0es apropiado porque la comparación está en una expresión
lógica.

Compara números con0Aunque es apropiado comparar expresiones lógicas implícitamente,


debe comparar expresiones numéricas explícitamente. Para números, escribe

mientras ( saldo != 0 ) ...

más bien que

mientras (equilibrio)...

Compara caracteres con el terminador nulo ('\0') explícitamente en C personajes, como


números, no son expresiones lógicas. Así, para los caracteres, escriba

mientras (*charPtr!= '\0')...

más bien que

mientras (*charPtr)...

Esta recomendación va en contra de la convención común de C para manejar datos de caracteres


(como en el segundo ejemplo aquí), pero refuerza la idea de que la expresión funciona con datos de
caracteres en lugar de datos lógicos. Algunas convenciones de C no se basan en maximizar la
legibilidad o la capacidad de mantenimiento, y este es un ejemplo de uno. Afortunadamente, todo
este problema se está desvaneciendo a medida que se escribe más código usando cadenas C++ y
STL.

Compara punteros con NULLPara punteros, escriba

mientras (bufferPtr != NULL ) ...


442 Capítulo 19: Problemas generales de control

más bien que

mientras (bufferPtr) ...

Al igual que la recomendación de caracteres, esta va en contra de la convención establecida en


C, pero la ganancia en legibilidad lo justifica.

Problemas comunes con las expresiones booleanas


Las expresiones booleanas están sujetas a algunas trampas adicionales que pertenecen a idiomas
específicos:

En lenguajes derivados de C, coloque constantes en el lado izquierdo de las comparacionesLos


lenguajes derivados de C plantean algunos problemas especiales con las expresiones booleanas. Si tiene
problemas para escribir mal=en vez de==, considere la convención de programación de poner constantes y
literales en el lado izquierdo de las expresiones, así:

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 ) ...

Esta recomendación entra en conflicto con la recomendación de usar la ordenación de líneas


numéricas. Mi preferencia personal es usar el orden de la línea numérica y dejar que el compilador
me advierta sobre asignaciones no deseadas.

En C++, considere crear sustituciones de macros de preprocesador para&&,||, y==


(pero solo como último recurso)Si tiene un problema de este tipo, es posible crear#
definirmacros para booleanosyyo, y useYyOen vez de&&y||. Del mismo modo, utilizando
= cuando quieres decir==es un error fácil de cometer. Si te pica a menudo este, puedes
crear una macro comoIGUALpara iguales lógicos (==).

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.

En Java, conoce la diferencia entrea==bya.igual(b)en Java,a==bpruebas de siaybse


refieren al mismo objeto, mientras quea.igual(b)comprueba si los objetos tienen el
mismo valor lógico. En general, los programas Java deben usar expresiones como
a.igual(b)más bien quea==b.

19.2 Sentencias Compuestas (Bloques)


Una "instrucción compuesta" o "bloque" es una colección de instrucciones que se tratan como una
instrucción única con el fin de controlar el flujo de un programa. Las declaraciones compuestas se
crean escribiendo{y}alrededor de un grupo de sentencias en C++, C#, C y Java. A veces están
implícitos en las palabras clave de un comando, comoParay próximoen Visual Basic. Las pautas para
usar declaraciones compuestas de manera efectiva son las siguientes:

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

llaves, corchetes y paréntesis. Para


aparatos ortopédicos oempezar-y-finalpares, y eso es un problema completamente innecesario. Si
obtener más información, consulte sigue esta guía, nunca más tendrá problemas para hacer coincidir esos pares.
"Edición" en la Sección 30.2.

Escribe esto primero:

for ( i = 0; i < maxLines; i++ )

Escribe esto a continuación:

for ( i = 0; i < maxLines; i++ ){ }

Escribe esto último:

for ( i = 0; i < maxLines; i++ ) {


// lo que sea que entre aquí . . .
}

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

444 Capítulo 19: Problemas generales de control

19.3 Sentencias Nulas


En C++, es posible tener una declaración nula, una declaración que consta completamente de un punto y coma, como
se muestra aquí:

Ejemplo en C++ de una declaración nula tradicional


while (recordArray.Read( index++ ) != recordArray.EmptyRecord() )
;

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

más información, consulte "Evitar


como lo harías con cualquier otra declaración. Este es el enfoque que se muestra en el ejemplo anterior.
bucles vacíos" en la Sección 16.2. Alternativamente, puede usar un conjunto de llaves vacías para enfatizar la declaración nula. Aquí hay dos
ejemplos:

Ejemplos de C++ de una declaración nula que se enfatiza


Esta es una forma de mostrar la while (recordArray.Read(index++)) != recordArray.EmptyRecord()) {}
declaración nula.
while (recordArray.Read( index++ ) != recordArray.EmptyRecord() ) {
;
Esta es otra forma de mostrarlo. }

Crear un preprocesadorHacer nada()macro o función en línea para declaraciones nulas La


declaración no hace nada más que dejar indiscutiblemente claro el hecho de que se supone que no
se debe hacer nada. Esto es similar a marcar páginas de documentos en blanco con la declaración
"Esta página se dejó en blanco intencionalmente". La página no está realmente en blanco, pero
sabes que no debe haber nada más en ella.

Así es como puede hacer su propia declaración nula en C++ usando#definir.


(También puede crearlo como unen líneafunción, que tendría el mismo efecto.)

Ejemplo en C++ de una declaración nula que se enfatiza conHacer nada()


# definir HacerNada()
...
while (recordArray.Read( index++ ) != recordArray.EmptyRecord() ) {
Hacer nada();
}

Además de usarHacer nada()en vacíotiempoyporbucles, puede usarlo para elecciones sin


importancia de uncambiardeclaración; incluidoHacer nada()deja en claro que el caso fue
considerado y se supone que no se debe hacer nada.
19.4 Domar anidamientos peligrosamente profundos 445

Si su idioma no admite macros de preprocesador o funciones en línea, puede crear un


Hacer nada()rutina que simplemente devuelve inmediatamente el control a la rutina de
llamada.

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++;

while ( registro != registroArray.EmptyRecord() ) {


registro = 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.

19.4 Domar anidamientos peligrosamente profundos


3 La sangría excesiva, o "anidamiento", ha sido ridiculizada en la literatura informática durante 25 años
2
1
y sigue siendo uno de los principales culpables de la confusión del código. Los estudios de Noam

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

tiene un anidamiento que es lo suficientemente profundo como para justificar la reestructuración:

Ejemplo de C++ de código mal anidado profundamente


if (estadoEntrada == EstadoEntrada_Éxito) {
// mucho código
CODIFICACIÓN ...
HORROR
if (rutina de impresora!= NULL) {
446 Capítulo 19: Problemas generales de control

Referencia cruzadaVolver a // mucho código


probar parte de la condición para ...
reducir la complejidad es similar a si (PáginaConfiguración()) {
volver a probar una variable de // mucho código
estado. Esa técnica se demuestra ...
en "Procesamiento de errores yir if ( AllocMem( &printData ) ) {
s” en la Sección 17.3. // mucho 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:

Ejemplo en C++ de código misericordiosamente desanidado al volver a probar

if (estadoEntrada == EstadoEntrada_Éxito) {
// mucho código
...
if (rutina de impresora!= NULL) {
// mucho código
...
}
}

if ( (estadoEntrada == EstadoEntrada_Éxito) &&


(printRoutine!= NULL) && SetupPage()) { // 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.

Simplificar un anidadosiusando undescansobloquearUna alternativa al enfoque que se


acaba de describir es definir una sección de código que se ejecutará como un bloque. Si
alguna condición en medio del bloque falla, la ejecución salta al final del bloque.

C++ Ejemplo de uso de undescansoBloquear


hacer {
// comienza el bloque de ruptura
if ( estado de entrada ! = estado de entrada_éxito ) {
descanso; // salir del bloque
}
19.4 Domar anidamientos peligrosamente profundos 447

// 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.

Convertir un anidadosia un conjunto desi-entonces-otrosSi piensas en un anidadosiprueba


críticamente, es posible que descubras que puedes reorganizarlo para que usesi-entonces-otros en lugar
de anidadosis. Suponga que tiene un árbol de decisión espeso como este:

Ejemplo de Java de un árbol de decisión demasiado grande


si ( 10 < cantidad ) {
si ( 100 < cantidad ) {
si ( 1000 < cantidad ) {
descuento = 0,10;
}
más {
descuento = 0,05;
}
}
más {
descuento = 0,025;
}
}
más {
descuento = 0,0;
}

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

Ejemplo de Java de un anidadosiConvertido en un conjunto desi-entonces-otros


si ( 1000 < cantidad ) {
descuento = 0,10;
}
si no ( 100 < cantidad ) {
descuento = 0,05;
}
si no ( 10 < cantidad ) {
descuento = 0,025;
}
más {
descuento = 0;
}

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:

Ejemplo de Java de un anidadosiConvertido en un conjunto desi-entonces-otros Cuando los


números son “desordenados”
si ( 1000 < cantidad ) {
descuento = 0,10;
}
de lo contrario si ( ( 100 < cantidad ) && ( cantidad <= 1000 ) ) {
descuento = 0,05;
}
de lo contrario si ( ( 10 < cantidad ) && ( cantidad <= 100 ) ) {
descuento = 0,025;
}
si no (cantidad <= 10) {
descuento = 0;
}

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.

Convertir un anidadosia uncasodeclaraciónPuede recodificar algunos tipos de pruebas, particularmente


aquellas con números enteros, para usar uncasodeclaración en lugar de cadenas desiarenamáss. No puede
usar esta técnica en algunos idiomas, pero es una técnica poderosa para aquellos en los que puede hacerlo.
Aquí se explica cómo recodificar el ejemplo en Visual Basic:

Ejemplo de Visual Basic de convertir un anidadosia uncasoDeclaración


Seleccione la cantidad de casos
Caso 0 a 10
descuento = 0.0
Caso 11 a 100
descuento = 0.025
19.4 Domar anidamientos peligrosamente profundos 449

Caso 101 a 1000


descuento = 0.05
caso más
descuento = 0.10
Final Seleccione

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.

Factorice el código profundamente anidado en su propia rutinaSi se produce un anidamiento profundo


dentro de un bucle, a menudo puede mejorar la situación colocando el interior del bucle en su propia rutina.
Esto es especialmente efectivo si el anidamiento es el resultado tanto de condicionales como de iteraciones.
Deja elsi-entonces-otrobifurca en el ciclo principal para mostrar la bifurcación de decisión y luego mueve las
declaraciones dentro de las bifurcaciones a sus propias rutinas. Este código necesita ser mejorado por tal
modificación:

Ejemplo de C++ de código anidado que debe dividirse en rutinas


while ( !Transacciones Completas() ) {
// leer registro de transacción transacción =
ReadTransaction();

// procesar la transacción según el tipo de transacción if (transaction.Type


== TransactionType_Deposit) {
// procesar un deposito
if (transacción.TipoDeCuenta == TipoDeCuenta_Comprobación) {
if ( transacción.AccountSubType == AccountSubType_Business )
MakeBusinessCheckDep(transacción.AccountNum, transacción.Cantidad); de lo contrario si
(transacción.AccountSubType == AccountSubType_Personal)
MakePersonalCheckDep( transacción.NúmCuenta, transacción.Cantidad ); de lo contrario si
(transacción.AccountSubType == AccountSubType_School)
MakeSchoolCheckDep( transacción.AccountNum, transacción.Cantidad );
}
de lo contrario si (transacción.AccountType == AccountType_Savings)
MakeSavingsDep( transacción.NúmCuenta, transacción.Cantidad ); de lo contrario si
(transacción.AccountType == AccountType_DebitCard)
MakeDebitCardDep( transacción.NúmCuenta, transacción.Cantidad ); de lo contrario si
(transacción.AccountType == AccountType_MoneyMarket)
MakeMoneyMarketDep( transacción.NúmCuenta, transacción.Cantidad ); de lo contrario si
(transacción.AccountType == AccountType_Cd)
MakeCDDep( transacción.NúmCuenta, transacción.Cantidad );
}
else if (transacción.Tipo == TipoTransacción_Retiro) {
// procesar un retiro
if (transacción.AccountType == AccountType_Checking)
MakeCheckingWithdrawal(transacción.AccountNum, transacción.Cantidad); de lo contrario si
(transacción.AccountType == AccountType_Savings)
HacerRetiroDeAhorros( transacción.NúmCuenta, transacción.Cantidad ); de lo contrario si
(transacción.AccountType == AccountType_DebitCard)
MakeDebitCardWithdrawal( transacción.NúmCuenta, transacción.Cantidad );
}
450 Capítulo 19: Problemas generales de control

Aquí esta la else if (transacción.Tipo == TipoTransacción_Transferencia) {


TransactionType_Transfer tipo HacerTransferenciaDeFondos(

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

proceso de programación del // procesar la transacción según el tipo de transacción if (transaction.Type


pseudocódigo". Las pautas para la == TransactionType_Deposit) {
descomposición funcional se dan ProcesarDeposito(
en "Divide y vencerás" en la transacción.AccountType,
Sección 5.4. transacción.AccountSubType,
transacción.AccountNum,
transacción.Amount
);
}
else if (transacción.Tipo == TipoTransacción_Retiro) {
ProcesarRetiro(
transacción.TipoCuenta,
transacción.NúmeroCuenta,
transacción.Importe
);
}
else if (transacción.Tipo == TipoTransacción_Transferencia) {
HacerTransferenciaDeFondos(
transacción.SourceAccountType,
transacción.TargetAccountType,
transacción.AccountNum,
cantidad de transacción
);
}
más {
// procesar el tipo de transacción desconocido LogTransactionError("Tipo de
transacción desconocido", transacción);
}
}
19.4 Domar anidamientos peligrosamente profundos 451

El código de las nuevas rutinas simplemente se extrajo de la rutina original y se transformó en


nuevas rutinas. (Las nuevas rutinas no se muestran aquí). El nuevo código tiene varias ventajas.
Primero, el anidamiento de dos niveles hace que la estructura sea más simple y fácil de
entender. En segundo lugar, puede leer, modificar y depurar el más cortotiempobucle en una
pantalla: no es necesario dividirlo en los límites de la pantalla o de la página impresa. Tercero,
poner la funcionalidad deProcesar depósito ()yProcesarRetiro()en rutinas acumula todas las
demás ventajas generales de la modularización. Cuarto, ahora es fácil ver que el código podría
dividirse en uncasodeclaración, lo que haría que sea aún más fácil de leer, como se muestra a
continuación:

C++ Ejemplo de buen código anidado después de la descomposición y el uso de un caso


Declaración
while ( !Transacciones Completas() ) {
// leer registro de transacción transacción =
ReadTransaction();

// procesar la transacción según el tipo de transacción switch


(transaction.Type) {
caso (TransactionType_Deposit):
ProcesarDeposito(
transacción.AccountType,
transacción.AccountSubType,
transacción.AccountNum,
transacción.Amount
);
descanso;

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

Utilice un enfoque más orientado a objetosUna forma sencilla de simplificar este


código en particular en un entorno orientado a objetos es crear un resumenTransacción
clase base y subclases paraDepósito,Retiro, yTransferir.

Ejemplo de C++ de buen código que usa polimorfismo


datos de transacción datos de transacción;
Transacción *transacción;

while ( !Transacciones Completas() ) {


// leer el registro de la transacción
transactionData = ReadTransaction();

// crea un objeto de transacción, según el tipo de cambio de transacción


(TransactionData.Type) {
caso (TransactionType_Deposit):
transacción = nuevo depósito (transactionData); descanso;

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;
}

En un sistema de cualquier tamaño, elcambiardeclaración se convertiría para utilizar un método de fábrica


que podría ser reutilizado en cualquier lugar un objeto deTransaccióntipo necesario para ser creado. Si este
código estuviera en un sistema de este tipo, esta parte sería aún más simple:

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".

while ( !Transacciones Completas() ) {


// leer el registro de transacciones y completar la transacción
transactionData = ReadTransaction();
transacción = Fábrica de transacciones. Crear (datos de transacción); transacción-
>Completa();
eliminar transacción;
}

Para el registro, el código en elTransactionFactory.Create()rutina es una simple


adaptación del código del ejemplo anteriorcambiardeclaración:
19.4 Domar anidamientos peligrosamente profundos 453

Ejemplo en C++ de buen código para una fábrica de objetos


Transacción *TransactionFactory::Create(
Datos de transacción Datos de
transacción ) {

// crea un objeto de transacción, según el tipo de cambio de transacción


(TransactionData.Type) {
caso (TransactionType_Deposit):
devolver nuevo depósito (transactionData);
descanso;

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;

}
}

Rediseñar código profundamente anidadoAlgunos expertos sostienen quecasoLas sentencias


virtualmente siempre indican un código mal factorizado en la programación orientada a objetos y rara vez, si
es que alguna, son necesarias (Meyer 1997). Esta transformación decasoLas declaraciones que invocan
rutinas a una fábrica de objetos con llamadas a métodos polimórficos son un ejemplo de ello.

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.

Resumen de técnicas para reducir el anidamiento profundo


La siguiente es una lista de las técnicas que puede usar para reducir el anidamiento profundo, junto
con referencias a las secciones de este libro que tratan las técnicas:

- Vuelva a probar parte de la condición (esta sección)

- Convertir asi-entonces-otros (esta sección)

- Convertir a uncasodeclaración (esta sección)

- Factorice el código profundamente anidado en su propia rutina (esta sección)

- Usar objetos y despacho polimórfico (esta sección)


454 Capítulo 19: Problemas generales de control

- 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)

- Usar excepciones (Sección 8.4)

- Rediseñar el código profundamente anidado por completo (esta sección)

19.5 Una base de programación: programación estructurada


El término "programación estructurada" se originó en un documento histórico, "Programación
estructurada", presentado por Edsger Dijkstra en la conferencia de la OTAN de 1969 sobre ingeniería
de software (Dijkstra 1969). Cuando llegó y se fue la programación estructurada, el término
"estructurado" se había aplicado a todas las actividades de desarrollo de software, incluido el análisis
estructurado, el diseño estructurado y el juego estructurado. Las diversas metodologías
estructuradas no estaban unidas por ningún hilo común, excepto que todas fueron creadas en un
momento en que la palabra "estructurado" les dio prestigio adicional.

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.

Un programa estructurado progresa de manera ordenada y disciplinada, en lugar de saltar de forma


impredecible. Puede leerlo de arriba a abajo y se ejecuta de la misma manera. Los enfoques menos
disciplinados dan como resultado un código fuente que proporciona una imagen menos significativa
y menos legible de cómo se ejecuta un programa en la máquina. Menos legibilidad significa menos
comprensión y, en última instancia, menor calidad del programa.

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.

Los tres componentes de la programación estructurada


Las próximas secciones describen las tres construcciones que constituyen el núcleo de la
programación estructurada.

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

Ejemplos Java de código secuencial


// una secuencia de sentencias de asignación a
= "1";
b = "2";
C = "3";

// una secuencia de llamadas a las rutinas


System.out.println( a );
Sistema.fuera.println( b );
Sistema.fuera.println( C );

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.

Acasoinstrucción es otro ejemplo de control de selección. loscambiarsentencia en C++ y Java y


laSeleccioneinstrucción en Visual Basic son todos ejemplos decaso. En cada caso, se selecciona
uno de varios casos para su ejecución. Conceptualmente,sideclaraciones y casodeclaraciones
son similares. Si su idioma no es compatiblecasosentencias, puede emularlas consi
declaraciones. Aquí hay dos ejemplos de selección:

Ejemplos de selección de Java


// selección en una sentencia if if
(totalAmount > 0.0) {
// hacer algo
...
}
más {
// hacer algo más . . .

// selección en un interruptor de declaración de


caso ( commandShortcutLetter ) {
caso 'a':
ImprimirInformeAnual();
descanso;
caso 'q':
ImprimirInformeTrimestral();
descanso;
casos':
ImprimirInformeResumen();
descanso;
defecto:
DisplayInternalError("Error interno 905: Llame al servicio de atención al cliente.");
}
456 Capítulo 19: Problemas generales de control

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:

Ejemplos de Visual Basic de iteración


' ejemplo de iteración usando un bucle For For index =
primero a último
Haz Algo (índice)
próximo

' ejemplo de iteración usando un bucle while índice =


primero
Mientras (índice <= último)
Hacer Algo ( índice ) índice =
índice + 1 Wend

' ejemplo de iteración usando un bucle con salida índice de bucle =


primero
Hacer

Si (índice > último) Entonces Salir Hacer


HacerAlgo (índice)
índice = índice + 1 Bucle

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.

19.6 Estructuras de control y complejidad


Una de las razones por las que se ha prestado tanta atención a las estructuras de control es que contribuyen en gran

medida a la complejidad general del programa. El mal uso de las estructuras de control aumenta la complejidad; el

buen uso lo disminuye.


19.6 Estructuras de control y complejidad 457

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.

Intuitivamente, la complejidad de un programa parecería determinar en gran medida la cantidad de


esfuerzo necesario para comprenderlo. Tom McCabe publicó un artículo influyente en el que argumentaba
que la complejidad de un programa se define por su flujo de control (1976). Otros investigadores han
PUNTO CLAVE
identificado factores distintos a la métrica de complejidad ciclomática de McCabe (como la cantidad de
variables utilizadas en una rutina), pero están de acuerdo en que el flujo de control es al menos uno de los
mayores contribuyentes a la complejidad, si no el más grande.

¿Qué tan importante es la complejidad?

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

del software: administrar la complejidad”


complejidad: “El programador competente es plenamente consciente del tamaño estrictamente
en la Sección 5.2. limitado de su propio cráneo; por lo tanto, aborda la tarea de programación con total
humildad” (Dijkstra 1972). Esto no implica que deba aumentar la capacidad de su cráneo para hacer
frente a una enorme complejidad. Implica que nunca podrá lidiar con una enorme complejidad y
debe tomar medidas para reducirla siempre que sea posible.

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.

Directrices generales para reducir la complejidad


Puede lidiar mejor con la complejidad de una de dos maneras. Primero, puede mejorar sus propias
habilidades de malabarismo mental haciendo ejercicios mentales. Pero la programación en sí misma
suele ser suficiente ejercicio, y las personas parecen tener problemas para hacer malabarismos con
más de cinco a nueve entidades mentales (Miller 1956). El potencial de mejora es pequeño. En
segundo lugar, puede disminuir la complejidad de sus programas y la cantidad de concentración
requerida para comprenderlos.
458 Capítulo 19: Problemas generales de control

Cómo medir la complejidad


Otras lecturaslos Probablemente tenga una idea intuitiva de lo que hace que una rutina sea más o menos compleja.
El enfoque descrito aquí se
Los investigadores han tratado de formalizar sus sentimientos intuitivos y han encontrado varias
basa en el influyente artículo
de Tom McCabe “A formas de medir la complejidad. Quizás la más influyente de las técnicas numéricas sea la de Tom
Complexity Measure” (1976). McCabe, en la que la complejidad se mide contando el número de "puntos de decisión" en una
rutina. La tabla 19-2 describe un método para contar los puntos de decisión.

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

3. Sume 1 para cada caso en uncasodeclaración.

Aquí hay un ejemplo:

if ( ( (estado = Éxito) y hecho) o


( no hecho y ( numLines >= maxLines ) ) ) entonces...

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.

Qué hacer con su medición de complejidad


Después de contar los puntos de decisión, puede usar el número para analizar la
complejidad de su rutina:

0–5 La rutina probablemente esté bien.

6–10 Empieza a pensar en formas de simplificar la rutina.


10+ Divida parte de la rutina en una segunda rutina y llámela desde la primera
rutina.

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.

El máximo de 10 puntos de decisión no es un límite absoluto. Utilice el número de puntos de decisión


como un indicador de advertencia que indica que es posible que sea necesario rediseñar una rutina.
No lo use como una regla inflexible. Acasodeclaración con muchos casos podría tener más de 10
elementos de largo y, dependiendo del propósito de lacasodeclaración, podría ser una tontería
romperla.
19.6 Estructuras de control y complejidad 459

Otros tipos de complejidad


Otras lecturasPara una excelente La medida de complejidad de McCabe no es la única medida sólida, pero es la medida más
discusión sobre las métricas de
discutida en la literatura informática y es especialmente útil cuando se piensa en el flujo de
complejidad, consulteMétricas y

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.

cc2e.com/1985 LISTA DE VERIFICACIÓN: Problemas de estructura de control

- ¿Usan las expresionesverdaderoyfalsomás bien que1y0?

- ¿Son valores booleanos comparados converdaderoyfalso¿implícitamente?

- ¿Se comparan explícitamente los valores numéricos con sus valores de prueba?

- ¿Se han simplificado las expresiones mediante la adición de nuevas variables


booleanas y el uso de funciones booleanas y tablas de decisión?

- ¿Las expresiones booleanas se expresan positivamente?

- ¿Se equilibran los pares de llaves?

- ¿Se usan aparatos ortopédicos en todas partes donde se necesitan para mayor claridad?

- ¿Las expresiones lógicas están completamente entre paréntesis?

- ¿Se escribieron las pruebas en orden de línea numérica?

- Hacer pruebas de usos de Javaa.igual(b)estilo en lugar deun == segundo¿cuando sea apropiado?

- ¿Son obvias las declaraciones nulas?

- ¿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.

- Minimizar la complejidad es la clave para escribir código de alta calidad.


Parte V
Mejoras de código

En esta parte:

Capítulo 20: El panorama de la calidad del software . . . . . . . . . . . . . . . . . . . . . .463


Capítulo 21: Construcción colaborativa . . . . . . . . . . . . . . . . . . . . . . . . . . .479 Capítulo
22: Pruebas del desarrollador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .499 Capítulo
23: Depuración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .535 Capítulo 24:
Refactorización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .563 Capítulo 25:
Estrategias de ajuste de código . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .587 Capítulo 26:
Técnicas de ajuste de códigos . . . . . . . . . . . . . . . . . . . . . . . . . . . . .609
capitulo 20

La calidad del software


Paisaje
cc2e.com/2036 Contenido

- 20.1 Características de la Calidad del Software: página 463

- 20.2 Técnicas para mejorar la calidad del software: página 466

- 20.3 Eficacia relativa de las técnicas de calidad: página 469

- 20.4 Cuándo hacer el control de calidad: página 473

- 20.5 El Principio General de la Calidad del Software: página 474

Temas relacionados

- Construcción colaborativa: Capítulo 21

- Pruebas de desarrollador: Capítulo 22

- Depuración: Capítulo 23

- Requisitos previos a la construcción: Capítulos 3 y 4

- ¿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.

20.1 Características de la Calidad del Software


El software tiene características de calidad tanto externas como internas. Las características externas
son características que un usuario del producto de software conoce, incluidas las siguientes:

- ExactitudEl grado en que un sistema está libre de fallas en su especificación,


diseño e implementación.

- 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.

- Fiabilidad La capacidad de un sistema para realizar sus funciones requeridas bajo


condiciones establecidas siempre que se requiera—tener un largo tiempo medio entre fallas.

- IntegridadEl grado en que un sistema evita el acceso no autorizado o inapropiado a sus


programas y sus datos. La idea de integridad incluye restringir los accesos de usuarios no
autorizados, así como garantizar que los datos se accedan correctamente, es decir, que las
tablas con datos paralelos se modifiquen en paralelo, que los campos de fecha contengan solo
fechas válidas, etc.

- AdaptabilidadLa medida en que un sistema se puede utilizar, sin modificaciones,


en aplicaciones o entornos distintos de aquellos para los que se diseñó
específicamente.

- Precisión El grado en que un sistema, tal como está construido, está libre de errores, especialmente

con respecto a los resultados cuantitativos. La precisión difiere de la corrección; es una


determinación de qué tan bien un sistema hace el trabajo para el que fue construido en lugar de
si fue construido correctamente.

- RobustezEl grado en que un sistema continúa funcionando en presencia de


entradas no válidas o condiciones ambientales estresantes.

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.

Las características externas de calidad son el único tipo de características de


software que interesan a los usuarios. A los usuarios les importa si el software es
fácil de usar, no si es fácil de modificar. Les importa si el software funciona
correctamente, no si el código es legible o está bien estructurado.

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

- mantenibilidadLa facilidad con la que puede modificar un sistema de software para


cambiar o agregar capacidades, mejorar el rendimiento o corregir defectos.

- FlexibilidadLa medida en que puede modificar un sistema para usos o entornos


distintos de aquellos para los que fue diseñado específicamente.

- 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.

- ReutilizaciónLa medida en que y la facilidad con la que puede utilizar partes de un


sistema en otros sistemas.

- LegibilidadLa facilidad con la que puede leer y comprender el código fuente de un


sistema, especialmente en el nivel de declaración detallada.
20.1 Características de la Calidad del Software 465

- TestabilidadEl grado en que puede realizar pruebas unitarias y de sistema de un


sistema; el grado en que puede verificar que el sistema cumple con sus requisitos.

- comprensibilidadLa facilidad con la que puede comprender un sistema tanto a nivel de


organización del sistema como de declaración detallada. La comprensibilidad tiene que
ver con la coherencia del sistema a un nivel más general que la legibilidad.

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.

20.2 Técnicas para mejorar la calidad del software


El aseguramiento de la calidad del software es un programa planificado y sistemático de actividades
diseñadas para asegurar que un sistema tenga las características deseadas. Aunque podría parecer que la
mejor manera de desarrollar un producto de alta calidad sería centrarse en el producto en sí, en el
aseguramiento de la calidad del software también debe centrarse en el proceso de desarrollo del software.
Algunos de los elementos de un programa de calidad de software se describen en las siguientes
subsecciones:

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.

Actividad explícita de control de calidadUn problema común al asegurar la calidad es que la


calidad se percibe como un objetivo secundario. De hecho, en algunas organizaciones, la
programación rápida y sucia es la regla y no la excepción. Los programadores como Global Gary, que
ensucian su código con defectos y "completan" sus programas rápidamente, son más
recompensados que los programadores como High-Quality Henry, que escriben programas
excelentes y se aseguran de que sean utilizables antes de publicarlos. En tales organizaciones, no
debería sorprender que los programadores no hagan de la calidad su primera prioridad. La
organización debe mostrar a los programadores que la calidad es una prioridad. Hacer explícita la
actividad de aseguramiento de la calidad aclara la prioridad y los programadores responderán en
consecuencia.
20.2 Técnicas para mejorar la calidad del software 467

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.

Revisiones técnicas informalesMuchos desarrolladores de software revisan su trabajo antes de entregarlo


para una revisión formal. Las revisiones informales incluyen la verificación de escritorio del diseño o el
código o la revisión del código con algunos compañeros.

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

el tipo de proyecto, consulte la


arquitectura son lo suficientemente buenos para admitir el desarrollo posterior.
Sección 3.2, “Determine el tipo de "Suficientemente bueno" puede significar que ha esbozado el 20 por ciento más crítico de los
software en el que está
requisitos o la arquitectura, o puede significar que ha especificado el 95 por ciento con un
trabajando”.
detalle insoportable; el extremo de la escala al que debe aspirar depende de la naturaleza. de
su proyecto específico.

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.

Medición de resultadosA menos que se midan los resultados de un plan de control de


calidad, no tendrá forma de saber si el plan está funcionando. La medición le dice si su plan es
un éxito o un fracaso y también le permite variar su proceso de forma controlada para ver
cómo se puede mejorar. También puede medir los atributos de calidad en sí mismos
(corrección, facilidad de uso, eficiencia, etc.) y es útil hacerlo. Para obtener detalles sobre la
medición de los atributos de calidad, consulte el Capítulo 9 dePrincipios de Ingeniería de
Software(Gilb 1988).

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.

Tabla 20-1 Clasificación del equipo en cada objetivo

Mínimo La mayoría La mayoría Mínimo


Se le dijo al equipo objetivo que memoria legible legible El menos programación
optimizara usar producción código código tiempo

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

Fuente: Adaptado de “Objetivos y rendimiento en la programación de computadoras” (Weinberg y Schulman


1974).

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.

20.3 Eficacia relativa de las técnicas de calidad


Las diversas prácticas de garantía de calidad no tienen todas la misma eficacia. Se
han estudiado muchas técnicas y se conoce su eficacia para detectar y eliminar
defectos. Este y varios otros aspectos de la "eficacia" de las prácticas de garantía de
calidad se analizan en esta sección.

Porcentaje de defectos detectados


Si los constructores construyeran edificios de la forma Algunas prácticas son mejores para detectar defectos que otras, y diferentes métodos encuentran
en que escribieron los programadores
diferentes tipos de defectos. Una forma de evaluar los métodos de detección de defectos es determinar el
programas, entonces el primer

pájaro carpintero que apareciera


porcentaje de defectos que detectan del total de defectos que existen en ese momento.
destruiría la civilización.
—Gerald Weinberg
470 Capítulo 20: El panorama de la calidad del software

punto en el proyecto. La tabla 20-2 muestra los porcentajes de defectos detectados por varias
técnicas comunes de detección de defectos.

Tabla 20-2 Tasas 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%

Revisiones informales de código 20% 25% 35%


Inspecciones formales de código Modelado o 45% 60% 70%
creación de prototipos Comprobación de 35% sesenta y cinco% 80%
escritorio personal del código Prueba unitaria 20% 40% 60%
15% 30% 50%
Prueba de nueva función (componente) 20% 30% 35%
Prueba de integración 25% 35% 40%
Test de regresión 15% 25% 30%
Prueba del sistema 25% 40% 55%
Prueba beta de bajo volumen (<10 sitios) 25% 35% 40%
Prueba beta de alto volumen (>1000 sitios) 60% 75% 85%
Fuente: Adaptado deProgramación Productividad(Jones 1986a), "Eficiencia de eliminación de defectos de
software" (Jones 1996) y "Lo que hemos aprendido sobre la lucha contra los defectos" (Shull et al. 2002).

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:

- Pruebas de ejecución contra la especificación

- Pruebas de ejecución contra la especificación con el código fuente

- Recorrido/inspección usando la especificación y el código fuente


20.3 Eficacia relativa de las técnicas de calidad 471

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

Tabla 20-3 Tasa estimada de detección de defectos de la programación extrema

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)

Revisiones informales de código 20% 25% 35%


(programación en pareja)

Comprobación de escritorio personal de código 20% 40% 60%


Prueba de unidad 15% 30% 50%
Examen de integración 25% 35% 40%
Test de regresión 15% 25% 30%
Eficiencia acumulada esperada de eliminación de ~74% ~90% ~97%
defectos

Costo de encontrar defectos

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).

Costo de Reparación de Defectos

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

cuanto más tiempo permanecen en un


para solucionarlo. Aún más importante, algunas técnicas, como las inspecciones, detectan los síntomas y las
sistema, consulte "Apelación a los causas de los defectos en un solo paso; otros, como las pruebas, encuentran síntomas pero requieren
datos" en la Sección 3.1. Para ver de
trabajo adicional para diagnosticar y corregir la causa raíz. El resultado es que las técnicas de un solo paso
cerca los errores en sí, consulte la
son sustancialmente más baratas en general que las de dos pasos.
Sección 22.4, “Errores típicos”.
20.4 Cuándo hacer el control de calidad 473

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.

La conclusión es que un programa efectivo de calidad de software debe incluir una


combinación de técnicas que se apliquen a todas las etapas de desarrollo. Esta es una
combinación recomendada para lograr una calidad superior a la media:

- Inspecciones formales de todos los requisitos, toda la arquitectura y los diseños de las partes críticas
de un sistema

- Modelado o creación de prototipos

- Lectura de códigos o inspecciones

- Pruebas de ejecución

20.4 Cuándo hacer el control de calidad


Referencia cruzadaAseguramiento de Como se señaló en el Capítulo 3 ("Medir dos veces, cortar una vez: requisitos previos de upstream"),
la calidad de las actividades upstream
cuanto antes se inserta un error en el software, más se enreda en otras partes del software y más
—requisitos y

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

474 Capítulo 20: El panorama de la calidad del software

20.5 El principio general de la calidad del software


No existe tal cosa como un almuerzo gratis, e incluso si lo hubiera, no hay garantía de que sea
bueno. El desarrollo de software está muy lejos dealta cocina, sin embargo, y la calidad del
software es inusual de manera significativa. El Principio General de la Calidad del Software es
PUNTO CLAVE
que mejorar la calidad reduce los costos de desarrollo.

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

escribir un producto de software,


día. Se incluyen el tiempo del probador, el tiempo del gerente de proyecto y el tiempo de apoyo
consulte "Programas, productos, administrativo. Las actividades que no son de codificación, como el desarrollo de requisitos y el trabajo de
sistemas y productos del sistema" en
arquitectura, también suelen tenerse en cuenta en esas cifras de líneas de código por día. Pero nada de eso
la Sección 27.5.
es lo que ocupa tanto tiempo.

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).

Un estudio de IBM produjo hallazgos similares:

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

3 El mismo efecto es cierto en el extremo pequeño de la escala. En un estudio de 1985, 166


2
1
programadores profesionales escribieron programas con la misma especificación. Los programas

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

100 500 más


500
Tiempo para completar el programa en minutos

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.

En comparación con el ciclo tradicional de código, prueba y depuración, un programa de calidad de


software ilustrado ahorra dinero. Redistribuye recursos fuera de la depuración y la refactorización en
actividades de control de calidad aguas arriba. Las actividades upstream tienen más influencia en la
calidad del producto que las actividades downstream, por lo que el tiempo que invierte upstream
ahorra más tiempo downstream. El efecto neto es menos defectos, menor tiempo de desarrollo y
menores costos. Verá varios ejemplos más del Principio general de la calidad del software en los
próximos tres capítulos.
476 Capítulo 20: El panorama de la calidad del software

cc2e.com/2043 LISTA DE VERIFICACIÓN: un plan de garantía de calidad


- ¿Ha identificado características de calidad específicas que son importantes para su
proyecto?

- ¿Ha informado a otros sobre los objetivos de calidad del proyecto?

- ¿Ha diferenciado entre características de calidad internas y externas?

- ¿Ha pensado en las formas en que algunas características pueden


competir con otras o complementarlas?

- ¿Requiere su proyecto el uso de varias técnicas diferentes de detección de errores


adecuadas para encontrar varios tipos diferentes de errores?

- ¿Su proyecto incluye un plan para tomar medidas para asegurar la calidad del
software durante cada etapa del desarrollo del software?

- ¿Se mide la calidad de alguna manera para saber si está


mejorando o empeorando?
- ¿Entiende la gerencia que el aseguramiento de la calidad incurre en costos adicionales por
adelantado para ahorrar costos más adelante?

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.

Lewis, Guillermo E.Pruebas de software y mejora continua de la calidad, 2ª ed. Auerbach


Publishing, 2000. Este libro ofrece un análisis completo del ciclo de vida de la calidad, así como
un análisis extenso de las técnicas de prueba. También proporciona numerosos formularios y
listas de verificación.
Puntos clave 477

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.

IEEE Std 1028-1997, estándar para revisiones de software.

IEEE Std 1008-1987 (R1993), estándar para pruebas de unidades de software.

IEEE Std 829-1998, Estándar para documentación de prueba de software.

Puntos clave
- La calidad es gratuita, al final, pero requiere una reasignación de recursos para que los defectos se

eviten de forma económica en lugar de repararlos de forma costosa.

- No todos los objetivos de garantía de calidad se pueden lograr simultáneamente. Decida


explícitamente qué objetivos desea alcanzar y comunique los objetivos a otras personas de su
equipo.

- 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.

- Puede aplicar técnicas efectivas durante la construcción y muchas técnicas


igualmente poderosas antes de la construcción. Cuanto antes encuentre un
defecto, menos entrelazado estará con el resto de su código y menos daño causará.

- El aseguramiento de la calidad en el campo del software está orientado al proceso. El


desarrollo de software no tiene una fase repetitiva que afecte el producto final como lo hace la
fabricación, por lo que la calidad del resultado está controlada por el proceso utilizado para
desarrollar el software.
capitulo 21

Construcción colaborativa
cc2e.com/2185 Contenido

- 21.1 Descripción general de las prácticas de desarrollo colaborativo: página 480

- 21.2 Programación por parejas: página 483

- 21.3 Inspecciones formales: página 485

- 21.4 Otros tipos de prácticas de desarrollo colaborativo: página 492

Temas relacionados

- El panorama de la calidad del software: Capítulo 20

- Pruebas de desarrollador: Capítulo 22

- Depuración: Capítulo 23

- Requisitos previos a la construcción: Capítulos 3 y 4

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.

Si ya ha leído acerca de las inspecciones y la programación de pares, no encontrará mucha


información nueva en este capítulo. El alcance de los datos concretos sobre la efectividad de
las inspecciones en la Sección 21.3 podría sorprenderlo, y es posible que no haya considerado
la alternativa de lectura de códigos descrita en la Sección 21.4. También puede consultar la
Tabla 21-1, "Comparación de técnicas de construcción colaborativa", al final del capítulo. Si su
conocimiento es todo de su propia experiencia, ¡siga leyendo! Otras personas han tenido
experiencias diferentes, y encontrarás algunas ideas nuevas.

479
480 Capítulo 21: Construcción colaborativa

21.1 Descripción general de las prácticas de desarrollo colaborativo


La "construcción colaborativa" se refiere a la programación en pares, las inspecciones formales, las
revisiones técnicas informales y la lectura de documentos, así como otras técnicas en las que los
desarrolladores comparten la responsabilidad de crear código y otros productos de trabajo. En mi empresa,
el término “construcción colaborativa” fue acuñado por Matt Peloquin alrededor del año 2000. El término
parece haber sido acuñado de forma independiente por otros en el mismo período de tiempo.

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.

La construcción colaborativa complementa otras técnicas de control


de calidad
3 El propósito principal de la construcción colaborativa es mejorar la calidad del software. Como se señaló en
2
1
el Capítulo 20, “El panorama de la calidad del software”, las pruebas de software tienen una eficacia limitada

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

- Hewlett-Packard informó que su programa de inspección ahorró aproximadamente


$21,5 millones por año (Grady y Van Slack 1994).

- 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).

- En una organización de mantenimiento de software, el 55 por ciento de los cambios de mantenimiento de

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

eran correctas la primera vez.

- El mismo grupo de personas desarrolló un grupo de 11 programas y todos se lanzaron a


producción. Los cinco primeros se desarrollaron sin revisiones y promediaron 4,5 errores por
cada 100 líneas de código. Los otros seis fueron inspeccionados y promediaron solo 0,82
errores por cada 100 líneas de código. Las revisiones redujeron los errores en más del 80 por
ciento (Freedman y Weinberg 1990).

- 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

La construcción colaborativa brinda tutoría en cultura corporativa y


experiencia en programación
Los procedimientos de revisión Los estándares de software se pueden escribir y distribuir, pero si nadie habla de ellos o alienta
informal se transmitieron de
a otros a usarlos, no se cumplirán. Las revisiones son un mecanismo importante para dar a los
persona a persona en la cultura
general de la informática durante
programadores comentarios sobre su código. El código, los estándares y las razones para
muchos años antes de que fueran hacer que el código cumpla con los estándares son buenos temas para las discusiones de
reconocido en forma impresa. La
revisión.
necesidad de revisar era tan obvia

para los mejores programadores


Además de la retroalimentación sobre qué tan bien siguen los estándares, los programadores
que rara vez la mencionaban en

forma impresa, mientras que los


necesitan retroalimentación sobre aspectos más subjetivos de la programación: formato,
peores programadores comentarios, nombres de variables, uso de variables locales y globales, enfoques de diseño, la forma
creían que eran tan buenos
en que hacemos las cosas aquí y pronto. Los programadores que todavía están húmedos detrás de
que su trabajo no necesitaba
revisión.
las orejas necesitan la guía de aquellos que tienen más conocimientos, y los programadores más
—Daniel Freedman y informados que tienden a estar ocupados deben ser alentados a dedicar tiempo a compartir lo que
Gerald Weinberg saben. Las revisiones crean un lugar para que los programadores más experimentados y menos
experimentados se comuniquen sobre problemas técnicos. Como tal, las revisiones son una
oportunidad para cultivar mejoras de calidad tanto en el futuro como en el presente.

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).

La propiedad colectiva se aplica a todas las formas de


construcción colaborativa
Referencia cruzadaUn concepto que Con la propiedad colectiva, todo el código es propiedad del grupo en lugar de individuos
y varios miembros del grupo pueden acceder a él y modificarlo. Esto produce varios
abarca todas las técnicas de construcción

colaborativa es la idea de propiedad

colectiva. En algunos modelos de


beneficios valiosos:
desarrollo, los programadores son dueños

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”.

Algunas metodologías, como Extreme Programming, recomiendan emparejar formalmente a los


ment.”
programadores y rotar sus asignaciones de trabajo a lo largo del tiempo. En mi empresa, descubrimos que
los programadores no necesitan emparejarse formalmente para lograr una buena cobertura de código. Con
el tiempo logramos una cobertura cruzada a través de una combinación de revisiones técnicas formales e
informales, programación en pares cuando es necesario y rotación de asignaciones de corrección de
defectos.
21.2 Programación por parejas 483

La colaboración se aplica tanto antes como después de la construcción


Este libro trata sobre la construcción, por lo que la colaboración en el diseño detallado y el código son el
enfoque de este capítulo. Sin embargo, la mayoría de los comentarios sobre la construcción colaborativa en
este capítulo también se aplican a las estimaciones, los planes, los requisitos, la arquitectura, las pruebas y
el trabajo de mantenimiento. Al estudiar las referencias al final del capítulo, puede aplicar técnicas de
colaboración a la mayoría de las actividades de desarrollo de software.

21.2 Programación por parejas


Cuando se programa en pareja, un programador escribe el código en el teclado y el otro
programador observa si hay errores y piensa estratégicamente si el código se está escribiendo
correctamente y si se está escribiendo el código correcto. La programación en parejas fue
originalmente popularizada por Extreme Programming (Beck 2000), pero ahora se usa más
ampliamente (Williams y Kessler 2002).

Claves del éxito con la programación en pareja

El concepto básico de la programación en pares es simple, pero su uso se beneficia de


algunas pautas:

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.

Beneficios de la programación en pareja

La programación en pareja produce numerosos beneficios:

- 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.

- Mejora la calidad del código. La legibilidad y comprensión del código


tiende a elevarse al nivel del mejor programador del equipo.

- 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.

- Produce todos los demás beneficios generales de la construcción colaborativa, incluida la


difusión de la cultura corporativa, la tutoría de programadores jóvenes y el fomento de la
propiedad colectiva.

cc2e.com/2192 LISTA DE VERIFICACIÓN: Programación en pareja efectiva


- ¿Tiene un estándar de codificación para que los programadores en pareja se mantengan enfocados

en la programación en lugar de en discusiones filosóficas de estilo de codificación?

- ¿Ambos socios participan activamente?

- ¿Está evitando programar en pareja todo y, en cambio, está seleccionando las tareas
que realmente se beneficiarán de la programación en pareja?

- ¿Está rotando asignaciones de pareja y asignaciones de trabajo con regularidad?

- ¿Las parejas están bien emparejadas en términos de ritmo y personalidad?

- ¿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

21.3 Inspecciones formales


Otras lecturasSi desea leer el Una inspección es un tipo específico de revisión que ha demostrado ser extremadamente eficaz para
artículo original sobre
detectar defectos y relativamente económica en comparación con las pruebas. Las inspecciones
inspecciones, consulte “Diseñar y

codificar inspecciones para


fueron desarrolladas por Michael Fagan y utilizadas en IBM durante varios años antes de que Fagan
reducir errores en el desarrollo de publicara el documento que las hizo públicas. Aunque cualquier revisión implica la lectura de diseños
programas” (Fagan 1976).
o código, una inspección difiere de una revisión común y corriente en varias formas clave:

- Las listas de verificación enfocan la atención de los revisores en áreas que han sido problemáticas en el

pasado.

- La inspección se enfoca en la detección de defectos, no en la corrección.

- Los revisores se preparan de antemano para la reunión de inspección y llegan con una lista
de los problemas que han descubierto.

- Se asignan roles distintos a todos los participantes.

- El moderador de la inspección no es el autor del producto de trabajo bajo


inspección.

- El moderador ha recibido formación específica en la moderación de inspecciones.

- La reunión de inspección se lleva a cabo solo si todos los participantes se han preparado adecuadamente.

- Los datos se recopilan en cada inspección y se introducen en futuras inspecciones para


mejorarlas.

- La gerencia general no asiste a la reunión de inspección a menos que esté inspeccionando un


plan de proyecto u otros materiales de gestión. Los líderes técnicos podrían asistir.

¿Qué resultados puede esperar de las inspecciones?


3 Las inspecciones individuales generalmente detectan alrededor del 60 por ciento de los defectos,
2
1
que es más alto que otras técnicas, excepto la creación de prototipos y las pruebas beta de gran

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.

Funciones durante una inspección

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:

ModeradorEl moderador es responsable de mantener la inspección en movimiento a un ritmo que sea lo


suficientemente rápido para ser productivo pero lo suficientemente lento para encontrar la mayor cantidad de
errores posibles. El moderador debe ser técnicamente competente, no necesariamente un experto en el diseño o
código particular bajo inspección, pero capaz de comprender los detalles relevantes. Esta persona gestiona otros
aspectos de la inspección, como la distribución del diseño o el código que se revisará, la distribución de la lista de
verificación de la inspección, la configuración de una sala de reuniones, la presentación de informes sobre los
resultados de la inspección y el seguimiento de los elementos de acción asignados en la reunión de inspección.

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.

En general, una inspección no debe tener menos de tres participantes. No es posible


tener un moderador, un autor y un revisor separados con menos de tres personas, y
esos roles no deben combinarse. El consejo tradicional es limitar una inspección a unas
seis personas porque, con más, el grupo se vuelve demasiado grande para manejarlo.
Los investigadores generalmente han descubierto que tener más de dos o tres revisores
no parece aumentar el número de defectos encontrados (Bush y Kelly 1989, Porter y
Votta 1997). Sin embargo, estos hallazgos generales no son unánimes y los resultados
parecen variar según el tipo de material que se inspeccione (Wiegers 2002). Preste
atención a su experiencia y ajuste su enfoque en consecuencia.

Procedimiento General para una Inspección

Una inspección consta de varias etapas bien diferenciadas:

PlanificaciónEl autor entrega el diseño o código al moderador. El moderador decide quién


revisará el material y cuándo y dónde tendrá lugar la reunión de inspección; el moderador
luego distribuye el diseño o código y una lista de verificación que enfoca la atención de los
inspectores. Los materiales deben estar impresos con números de línea para acelerar la
identificación de errores durante la reunión.

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

o el diseñador, por ejemplo. La investigación sobre revisiones basadas en perspectivas no ha sido


exhaustiva, pero sugiere que las revisiones basadas en perspectivas podrían descubrir más errores que
las revisiones generales.

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.

Reporte de inspecciónDentro de un día de la reunión de inspección, el moderador produce un


informe de inspección (correo electrónico o equivalente) que enumera cada defecto, incluido su tipo
y gravedad. El informe de inspección ayuda a asegurar que todos los defectos serán corregidos, y
21.3 Inspecciones formales 489

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.

Reunión de la tercera horaAunque durante la inspección no se permite a los participantes discutir


soluciones a los problemas planteados, es posible que algunos quieran hacerlo. Puede realizar una reunión
informal de tres horas para permitir que las partes interesadas discutan soluciones después de que termine
la inspección oficial.

Ajuste fino de la inspección

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

programación informática, 2ª ed.


nocriticar al autor del diseño o código. La experiencia debe ser positiva para el autor en la
(Weinberg 1998). que es obvio que la participación del grupo mejora el programa y es una experiencia de
aprendizaje para todos los involucrados. No debe convencer al autor de que algunas
personas en el grupo son idiotas o que es hora de buscar un nuevo trabajo. Comentarios
como "Cualquiera que conozca Java sabe que es más eficiente hacer un bucle desde0a
número-1, no1anúmero” son totalmente inapropiados, y si ocurren, el moderador debe
dejar en claro inequívocamente su inadecuación.

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.

Inspecciones yCódigo completo


Tuve una experiencia personal usando inspecciones en la segunda edición deCódigo completo. Para
la primera edición de este libro inicialmente escribí un borrador. Después de dejar el borrador de
cada capítulo en un cajón durante una semana o dos, volví a leer el capítulo en frío y corregí los
errores que encontré. Luego distribuí el capítulo revisado a alrededor de una docena de colegas para
que lo revisaran, varios de los cuales lo revisaron completamente. Corregí los errores que
encontraron. Después de algunas semanas más, lo revisé nuevamente y corregí más errores.
Finalmente, envié el manuscrito al editor, donde fue revisado por un corrector de estilo, un editor
técnico y un corrector de pruebas. El libro estuvo impreso durante más de 10 años y los lectores
enviaron unas 200 correcciones durante ese tiempo.

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,

consulteGestión del proceso de


1995). El proceso de inspección demuestra cómo es el nivel más alto. El proceso es sistemático
software(Humphrey 1989). y repetible y utiliza retroalimentación medida para mejorar. Puede aplicar las mismas ideas a
muchas de las técnicas descritas en este libro. Cuando se generalizan a toda una organización
de desarrollo, estas ideas son, en pocas palabras, lo que se necesita para llevar a la
organización al nivel más alto posible de calidad y productividad.

cc2e.com/2199 LISTA DE VERIFICACIÓN: Inspecciones efectivas


- ¿Tiene listas de verificación que enfocan la atención del revisor en áreas que han
sido problemáticas en el pasado?

- ¿Ha centrado la inspección en la detección de defectos en lugar de la corrección?

- ¿Ha considerado asignar perspectivas o escenarios para ayudar a los revisores a


enfocar su trabajo de preparación?

- ¿Se les da suficiente tiempo a los revisores para prepararse antes de la reunión de inspección
y está cada uno preparado?

- ¿Cada participante tiene un papel distinto que desempeñar: moderador, revisor,


escribiente, etc.?

- ¿La reunión se mueve a un ritmo productivo?

- ¿La reunión está limitada a dos horas?

- ¿Todos los participantes de la inspección han recibido capacitación específica en la realización de


inspecciones y el moderador ha recibido capacitación especial en habilidades de moderación?

- ¿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?

- ¿Los elementos de acción asignados en cada inspección son seguidos, ya sea


personalmente por el moderador o con una reinspección?

- ¿Entiende la dirección que no debe asistir a las reuniones de inspección?

- ¿Existe un plan de seguimiento para garantizar que las correcciones se realicen correctamente?

21.4 Otros tipos de prácticas de desarrollo colaborativo


Otros tipos de colaboración no han acumulado el cuerpo de apoyo empírico que tienen las
inspecciones o la programación en pares, por lo que aquí se tratan con menos profundidad. Las
colaboraciones cubiertas en esta sección incluyen recorridos, lectura de códigos y espectáculos de
perros y ponis.

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 se centra en cuestiones técnicas: es una reunión de trabajo.

- Todos los participantes se preparan para el recorrido leyendo el diseño o el código y


buscando errores.

- 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.

- Un recorrido por lo general dura de 30 a 60 minutos.

- El énfasis está en la detección de errores, no en la corrección.


21.4 Otros tipos de prácticas de desarrollo colaborativo 493

- 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.

¿Qué resultados puede esperar de un recorrido?


Usado inteligentemente y con disciplina, un recorrido puede producir resultados similares a los de
una inspección, es decir, normalmente puede encontrar entre el 20 y el 40 por ciento de los errores
en un programa (Myers 1979, Boehm 1987b, Yourdon 1989b, Jones 1996 ). Pero, en general, se ha
encontrado que los recorridos son significativamente menos efectivos que las inspecciones (Jones
1996).

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

La lectura de códigos es una alternativa a las inspecciones y recorridos. En la lectura de código,


lee el código fuente y busca errores. También comenta sobre aspectos cualitativos del código,
como su diseño, estilo, legibilidad, mantenibilidad y eficiencia.

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

proyecto que los diversos tipos de pruebas.

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.

3 Un estudio de 13 revisiones en AT&T encontró que la importancia de la reunión de revisión en sí estaba


2
1
sobrevalorada; El 90 por ciento de los defectos se encontraron durante la preparación de la reunión de

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

Espectáculos de perros y ponis

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.

Comparación de Técnicas de Construcción Colaborativa


¿Cuáles son las diferencias entre los distintos tipos de construcción colaborativa? La tabla 21-1
proporciona un resumen de las principales características de cada técnica.

Tabla 21-1 Comparación de Técnicas de Construcción Colaborativa


Par Formal Revisión informal
Propiedad Programación Inspección (Recorridos)
Roles de participantes definidos Sí Sí No
Capacitación formal sobre cómo Tal vez, a través Sí No
desempeñar los roles. entrenamiento

Quién “dirige” la colaboración persona con el Moderador Autor, por lo general


teclado
Foco de colaboración Diseño, codificación, Detección de defectos Varía
prueba y defecto ción solamente

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

Mejora de la eficiencia del proceso a partir No Sí No


del análisis de los resultados.

Útil para actividades no relacionadas con la Posiblemente Sí Sí


construcción

Porcentaje típico de defectos 40–60% 45–70% 20-40%


encontrados
496 Capítulo 21: Construcción colaborativa

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.

Si la programación en pares y las inspecciones formales producen resultados similares en cuanto a


calidad, costo y cronograma, la elección entre ellos se convierte en una cuestión de estilo personal
más que de sustancia técnica. Algunas personas prefieren trabajar solas, y solo ocasionalmente
salen del modo solo para las reuniones de inspección. Otros prefieren pasar más tiempo trabajando
directamente con otros. La elección entre las dos técnicas puede ser impulsada por la preferencia de
estilo de trabajo de los desarrolladores específicos de un equipo, y los subgrupos dentro del equipo
pueden elegir de qué manera les gustaría hacer la mayor parte de su trabajo. También debe usar
diferentes técnicas con un proyecto, según corresponda.

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.

Beck, Kent.Explicación de la programación extrema: aceptar el cambio. Reading, MA: Addison


Wesley, 2000. Este libro aborda brevemente la programación en pares y muestra cómo se
puede usar junto con otras técnicas de apoyo mutuo, incluidos los estándares de codificación,
la integración frecuente y las pruebas de regresión.

Reifer, Donald. “Cómo aprovechar al máximo la programación extrema/métodos ágiles”, Actas,


XP/Agile Universe 2002. Nueva York, Nueva York: Springer; págs. 185–196. Este artículo resume
la experiencia industrial con Programación Extrema y métodos ágiles y presenta claves para el
éxito de la programación en pareja.

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

Gilb, Tom y Dorothy Graham.Inspección de software. Wokingham, Inglaterra: Addison-Wesley, 1993.


Contiene un análisis exhaustivo de las inspecciones a principios de la década de 1990. Tiene un
enfoque práctico e incluye estudios de casos que describen experiencias que varias organizaciones
han tenido al establecer programas de inspección.

Fagan, Michael E. "Inspecciones de diseño y código para reducir errores en el desarrollo de


programas".Diario de sistemas de IBM15, núm. 3 (1976): 182–211.

Fagan, Michael E. "Avances en las inspecciones de software".Transacciones IEEE sobre ingeniería de


software, SE-12, núm. 7 (julio de 1986): 744–51. Estos dos artículos fueron escritos por el
desarrollador de las inspecciones. Contienen la esencia de lo que necesita saber para realizar una
inspección, incluidos todos los formularios de inspección estándar.

Estándares relevantes

IEEE Std 1028-1997, estándar para revisiones de software

IEEE Std 730-2002, Estándar para Planes de Garantía de Calidad de Software

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.

- Las inspecciones formales se pueden utilizar en productos de trabajo como requisitos,


diseños y casos de prueba, así como en código.

- 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

- 22.2 Enfoque recomendado para las pruebas de desarrollador: página 503

- 22.3 Bolsa de trucos de prueba: página 505

- 22.4 Errores típicos: página 517

- 22.5 Herramientas de soporte de pruebas: página 523

- 22.6 Mejorar sus pruebas: página 528

- 22.7 Mantenimiento de registros de pruebas: página 529

Temas relacionados

- El panorama de la calidad del software: Capítulo 20

- Prácticas de construcción colaborativa: Capítulo 21

- Depuración: Capítulo 23

- Integración: Capítulo 29

- Requisitos previos a la construcción: Capítulo 3

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:

- Examen de la unidades la ejecución de una clase, rutina o programa pequeño completo


que ha sido escrito por un solo programador o equipo de programadores, que se
prueba de forma aislada del sistema más completo.

- Prueba de componenteses la ejecución de una clase, paquete, programa pequeño u


otro elemento de programa que implica el trabajo de varios programadores o equipos
de programación, que se prueba de forma aislada del sistema más completo.

- Pruebas de integraciónes la ejecución combinada de dos o más clases, paquetes,


componentes o subsistemas que han sido creados por múltiples programadores o equipos de
programación. Este tipo de prueba generalmente comienza tan pronto como hay dos clases
para probar y continúa hasta que se completa todo el sistema.

499
500 Capítulo 22: Pruebas de desarrollador

- Pruebas de regresiónes la repetición de casos de prueba ejecutados previamente con el fin de


encontrar defectos en el software que previamente pasó el mismo conjunto de pruebas.

- 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.

22.1 Papel de las pruebas de desarrollador en la calidad del software


Referencia cruzadaPara obtener detalles La prueba es una parte importante de cualquier programa de calidad de software y, en muchos casos, es la
sobre las revisiones, consulte el Capítulo
única parte. Esto es desafortunado, porque se ha demostrado que las prácticas de desarrollo colaborativo
21, “Construcción colaborativa”.
en sus diversas formas encuentran un mayor porcentaje de errores que las pruebas, y cuestan menos de la
mitad por error encontrado que las pruebas (Card 1987, Russell 1991, Kaplan 1995). . Los pasos de prueba
individuales (prueba unitaria, prueba de componentes y prueba de integración) generalmente encuentran
menos del 50 por ciento de los errores presentes en cada uno. La combinación de pasos de prueba a
menudo encuentra menos del 60 por ciento de los errores presentes (Jones 1998).

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

programas con errores. Los


actividad difícil de tragar para la mayoría de los desarrolladores por varias razones:
programadores deben insertarlos.

— 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.

Pruebas durante la construcción


El gran y amplio mundo de las pruebas a veces ignora el tema de este capítulo: las pruebas de "caja
blanca" o "caja de cristal". Por lo general, desea diseñar una clase para que sea una caja negra: un
usuario de la clase no tendrá que mirar más allá de la interfaz para saber qué hace la clase. Sin
embargo, al probar la clase, es ventajoso tratarla como una caja de cristal, para observar el código
fuente interno de la clase, así como sus entradas y salidas. Si sabe lo que hay dentro de la caja, puede
probar la clase más a fondo. Por supuesto, también tiene los mismos puntos ciegos al probar la clase
que tenía al escribirla, por lo que la prueba de caja negra también tiene ventajas.

Durante la construcción, generalmente escribe una rutina o clase, la revisa mentalmente y


luego la revisa o prueba. Independientemente de su estrategia de integración o prueba del
sistema, debe probar cada unidad a fondo antes de combinarla con otras. Si está escribiendo
varias rutinas, debe probarlas una a la vez. Las rutinas no son realmente más fáciles de probar
individualmente, pero son mucho más fáciles de depurar. Si junta varias rutinas no probadas a
la vez y encuentra un error, cualquiera de las varias rutinas podría ser culpable. Si agrega una
rutina a la vez a una colección de rutinas probadas previamente, sabrá
22.2 Enfoque recomendado para las pruebas de desarrollador 503

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.

22.2 Enfoque recomendado para las pruebas de desarrollador


Un enfoque sistemático para las pruebas de desarrollador maximiza su capacidad para detectar errores de
todo tipo con un mínimo de esfuerzo. Asegúrese de cubrir este terreno:

- 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

cometido en proyectos anteriores.

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.

¿Prueba primero o prueba última?

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

504 Capítulo 22: Pruebas de desarrollador

- 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.

Limitaciones de las pruebas de desarrollador

Esté atento a las siguientes limitaciones con las pruebas de desarrollador:

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

22.3 Bolsa de trucos de prueba


¿Por qué no es posible probar que un programa es correcto probándolo? Para usar las pruebas
para probar que un programa funciona, tendría que probar cada valor de entrada concebible
para el programa y cada combinación concebible de valores de entrada. Incluso para
programas simples, tal empresa se volvería masivamente prohibitiva. Suponga, por ejemplo,
que tiene un programa que toma un nombre, una dirección y un número de teléfono y los
almacena en un archivo. Este es ciertamente un programa simple, mucho más simple que
cualquiera cuya corrección realmente le preocupe. Suponga además que cada uno de los
nombres y direcciones posibles tiene 20 caracteres y que hay 26 caracteres posibles para usar
en ellos. Este sería el número de entradas posibles:

Nombre 2620(20 caracteres, cada uno con 26 opciones posibles)


Dirección 2620(20 caracteres, cada uno con 26 opciones posibles)
Número de teléfono 1010(10 dígitos, cada uno con 10 opciones posibles) = 26
Posibilidades totales 20* 2620* 1010≈1066

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

detalles, consulte "Monitores de


prueba, es probable que solo unos pocos revelen errores que los demás no. Debes concentrarte en elegir
cobertura" en la Sección 22.5, unos pocos que te digan cosas diferentes en lugar de un conjunto que te diga lo mismo una y otra vez.
"Herramientas de soporte de pruebas",

más adelante en este capítulo.

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.

Pruebas de base estructurada

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

a lo complicada que es la expresión dentro de lasiotiempoes asegurarse de que la declaración


esté completamente probada. La forma más fácil de asegurarse de haber cubierto todas las
bases es calcular la cantidad de rutas a través del programa y luego desarrollar la cantidad
mínima de casos de prueba que ejercitarán cada ruta a través del programa.

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

complejidad en "Cómo medir la


1.Comience con 1 para el camino directo a través de la rutina.
complejidad" en la Sección 19.6.

2.Agregue 1 para cada una de las siguientes palabras clave, o sus equivalentes:si,tiempo,
repetir, por,y, yo.

3.Sume 1 para cada caso en uncasodeclaración. Si elcasodeclaración no tiene un caso


predeterminado, agregue 1 más.

Aquí hay un ejemplo:

Ejemplo simple de cálculo del número de rutas a través de un programa Java


Cuente “1” para la rutina en Declaración1;
sí. Declaración2;
si ( x < 10 ) {
Cuente “2” para elsi. Declaración3;
}
Declaración4;

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:

- Declaraciones controladas porsise ejecutan (x < 10).

- Declaraciones controladas porsino se ejecutan (x >= 10).

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

El siguiente listado es un ejemplo un poco más complicado. Este fragmento de código se


utiliza a lo largo del capítulo y contiene algunos errores posibles.

Ejemplo de cálculo del número de casos necesarios para la prueba básica de un


programa Java
Cuente “1” para la rutina en 1 // Calcular Retenciones totales
sí. 2 de pago neto = 0;
3
Cuente “2” para elpor. 4 for ( id = 0; id < número de empleados; id++ ) {
5
6 // calcule la retención del seguro social, si está por debajo del máximo
Cuente “3” para elsi. 7 if ( m_employee[ id ].governmentRetirementWithheld < MAX_GOVT_RETIREMENT ) {
8 jubilación del gobierno = ComputeGovernmentRetirement( m_employee[ id ] );
9 }
10
11 // establece el valor predeterminado sin aportes para la jubilación
12 companyRetirement = 0;
13
14 // determinar la contribución de jubilación discrecional del empleado si
Cuente “4” para elsiy “5” 15 ( m_employee[ id ].WantsRetirement &&
para el&&. dieciséis Elegible para la jubilación (m_employee [ id ] ) ) { companyRetirement =
17 GetRetirement ( m_employee [ id ] );
18 }
19
20 brutoPay = ComputeGrossPay ( m_employee [ id ] );
21
22 // determina la contribución de IRA
23 personalRetirement = 0;
Cuente “6” para elsi. 24 if ( Elegible para la jubilación personal ( m_employee [ id ] ) ) {
25 retiropersonal = ContribuciónRetiroPersonal( m_employee[ id ],
26 empresaRetiro, brutoPay);
27 }
28
29 // hacer cheque de pago semanal
30 retención = ComputeWithholding( m_employee[ id ] );
31 netPay = grossPay - retención - companyJubilación - gobiernoJubilación –
32 retiro personal;
33 PayEmployee( m_employee[ id ], netPay );
508 Capítulo 22: Pruebas de desarrollador

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:

Caso Descripción de la prueba Datos de prueba

1 Caso nominal Todas las condiciones booleanas son verdaderas

2 La inicialporla número de empleados < 1

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.

Pruebas de flujo de 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

inicializaciones de datos (Beizer 1990).

Los datos pueden existir en uno de tres estados:

- definidoLos datos se han inicializado, pero aún no se han utilizado.

- 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.

Combinaciones de estados de datos

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

- Definido-SalidoSi la variable es una variable local, no tiene sentido definirla y


salir sin usarla. Si es un parámetro de rutina o una variable global, podría estar
bien.

- Definido-asesinadoDefinir una variable y luego eliminarla sugiere que la


variable es extraña o que falta el código que se suponía que debía usar la
variable.

- Entrado-MatadoEsto es un problema si la variable es una variable local. No sería


necesario eliminarlo si no se ha definido o utilizado. Si, por el contrario, es un parámetro
de rutina o una variable global, este patrón está bien siempre que la variable se defina
en otro lugar antes de eliminarla.

- Ingresado-UsadoNuevamente, esto es un problema si la variable es una variable


local. La variable debe definirse antes de usarse. Si, por otro lado, es un parámetro
de rutina o una variable global, el patrón está bien si la variable se define en otro
lugar antes de usarla.

- 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.

- Muerto-UsadoUsar una variable después de que se haya eliminado es un error lógico. Si el


código parece funcionar de todos modos (por ejemplo, un puntero que todavía apunta a la
memoria que se ha liberado), eso es un accidente, y la Ley de Murphy dice que el código
dejará de funcionar en el momento en que cause el mayor caos.

- Usado-DefinidoUsar y luego definir una variable puede o no ser un problema,


dependiendo de si la variable también se definió antes de usarla. Ciertamente,
si ve un patrón definido por el uso, vale la pena buscar una definición
anterior.

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

Aquí hay un ejemplo:

Ejemplo Java de un programa cuyo flujo de datos se va a probar


si ( Condición 1 ) {
X = a;
}
más {
X = b;
}
si (Condición 2) {
y = x + 1;
}
más {
y = x - 1;
}

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

completo de casos de prueba de flujo de datos definidos y usados.


512 Capítulo 22: Pruebas de desarrollador

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

pruebas de base estructurada:

Caso Descripción de la prueba

7 DefinirEmpresaRetiroen la línea 12 y utilícelo primero en la línea 26. Esto no está

necesariamente cubierto por ninguno de los casos de prueba anteriores. Definir

8 EmpresaRetiroen la línea 12 y utilícelo primero en la línea 31. Esto no está

necesariamente cubierto por ninguno de los casos de prueba anteriores. Definir

9 EmpresaRetiroen la línea 17 y utilícelo primero en la línea 31. Esto no está

necesariamente cubierto por ninguno de los casos de prueba anteriores.

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.

Pensar en la partición de equivalencia no le dará mucha información nueva sobre un


programa cuando ya ha cubierto el programa con pruebas de base y flujo de datos. Sin
embargo, es especialmente útil cuando observa un programa desde el exterior (desde
una especificación en lugar del código fuente) o cuando los datos son complicados y las
complicaciones no se reflejan en la lógica del programa.
22.3 Bolsa de trucos de prueba 513

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.

El ejemplo de código en la página 507 contiene una comprobación dem_empleado[ ID ].g


retiro del gobierno retenido <MAX_GOVT_RETIREMENT. De acuerdo con los principios del
análisis de límites, se deben examinar tres casos:

Caso Descripción de la prueba

1 El caso 1 se define de modo que la verdadera condición para


m_employee[ ID ].governmentRetirementWithheld < MAX_GOVT_RETIREMENTes el
primer caso en el lado verdadero de la frontera. Por lo tanto, el caso de prueba del Caso
1 establecem_employee[ ID ].governmentRetirementWithheld to
MAX_GOVT_RETIREMENT – 1. Este caso de prueba ya fue generado.
514 Capítulo 22: Pruebas de desarrollador

3 El caso 3 se define de modo que la condición falsa para


m_empleado[ identificación ].retiro del gobierno retenido <
MAX_GOVT_RETIREMENTestá en el lado falso del límite. Por lo tanto, el caso de
prueba del Caso 3 establecem_employee[ ID ].governmentRetirementWithhelda
MAX_GOVT_RETIREMENT + 1. Este caso de prueba también ya se generó.
10 Se agrega un caso de prueba adicional para el caso directamente en el límite en
el quem_employee [ ID ].governmentRetirementWithheld =
MAX_GOVT_RETIREMENT.

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:

Caso Descripción de la 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:

Caso Descripción de la prueba

12 Un grupo de 10 empleados, cada uno de los cuales tiene un salario de $0.00.

Clases de datos incorrectos

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

- Muy pocos datos (o ningún dato)

- demasiados datos
22.3 Bolsa de trucos de prueba 515

- El tipo incorrecto de datos (datos no válidos)

- El tamaño incorrecto de los datos

- 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:

Caso Descripción de la prueba

13 Una matriz de 100.000.000 de empleados. Pruebas para demasiados datos. Por


supuesto, cuánto es demasiado variará de un sistema a otro, pero por el bien del
ejemplo, suponga que esto es demasiado.
14 Un salario negativo. Tipo de datos incorrecto.

15 Un número negativo de empleados. Tipo de datos incorrecto.

Clases de buenos datos


Cuando intenta encontrar errores en un programa, es fácil pasar por alto el hecho de que el caso
nominal puede contener un error. Por lo general, los casos nominales descritos en la sección de
prueba de base representan un tipo de buenos datos. Los siguientes son otros tipos de buenos datos
que vale la pena revisar. Verificar cada uno de estos tipos de datos puede revelar errores, según el
elemento que se esté probando.

- Casos nominales: valores esperados intermedios

- Configuración normal mínima

- Configuración normal máxima

- Compatibilidad con datos antiguos

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:

Caso Descripción de la prueba

dieciséis Un grupo de un empleado. Para probar la configuración mínima normal.

La configuración normal máxima es la opuesta a la mínima. Es similar en espíritu a la prueba


de límites, pero nuevamente, crea un conjunto de valores máximos fuera del conjunto.
516 Capítulo 22: Pruebas de desarrollador

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:

Caso Descripción de la prueba

17 Un grupo de 500 empleados. Para probar la configuración normal máxima.

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.

Utilice casos de prueba que faciliten las verificaciones manuales

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

22.4 Errores típicos


Esta sección está dedicada a la proposición de que puedes probar mejor cuando sabes tanto como
sea posible sobre tu enemigo: los errores.

¿Qué clases contienen la mayoría de los errores?

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).

Errores por Clasificación


Referencia cruzadaPara obtener una Varios investigadores han tratado de clasificar los errores por tipo y determinar en qué medida
lista de todas las listas de verificación
ocurre cada tipo de error. Todo programador tiene una lista de errores que han sido
del libro, consulte la lista que sigue al

índice del libro.


especialmente problemáticos: errores de uno en uno, olvido de reinicializar una variable de
bucle, etc. Las listas de verificación presentadas a lo largo del libro brindan más detalles.

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

16,19% Funcionalidad implementada


9,88% Construcción
8,98% Integración
8,12% Requisitos funcionales Definición o
2,76% ejecución de pruebas Sistema,
1,74% arquitectura de software Sin
4,71% especificar

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é

roto. Y la base de datos


son causados por programadores, el 2 % por software de sistemas (el compilador y el sistema operativo), el
probablemente esté bien. 2 % por algún otro software y el 1 % por el hardware (Brown y Sampson 1973, Ostrand y Weyuker 1984). El
—andy caza y software de sistemas y las herramientas de desarrollo son utilizados por muchas más personas hoy en día
david thomas
que en los años 70 y 80, por lo que mi mejor suposición es que, hoy en día, un porcentaje aún mayor de
errores son culpa de los programadores.

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 incomprensión del diseño es un tema recurrente en los estudios de errores del


programador El estudio de compilación de Beizer, por lo que vale, encontró que el 16% de los
errores surgieron de malas interpretaciones del diseño (Beizer 1990). Otro estudio encontró
que el 19% de los errores resultaron de un diseño mal entendido (Weiss 1975). Vale la pena
tomarse el tiempo necesario para comprender el diseño a fondo. Ese tiempo no produce
dividendos inmediatos, no necesariamente parece que esté trabajando, pero vale la pena
durante la vida del proyecto.

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.

Proporción de errores resultantes de una construcción defectuosa

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.

Aquí están mis conclusiones:

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 defectos de construcción representan al menos el 35% de todos los defectos,


independientemente del tamaño del proyecto. Aunque la proporción de defectos de construcción es
menor en proyectos grandes, aún representan al menos el 35% de todos los defectos (Beizer 1990,
Jones 2000). Algunos investigadores han informado proporciones en el rango del 75% incluso en
proyectos muy grandes (Grady 1987). En general, cuanto mejor se comprenda el área de aplicación,
mejor será la arquitectura general. Entonces, los errores tienden a concentrarse en el diseño y la
codificación detallados (Basili y Perricone 1984).

- 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.

¿Cuántos errores debe esperar encontrar?


El número de errores que debe esperar encontrar varía según la calidad del proceso de
desarrollo que utilice. Este es el abanico de posibilidades:

3 - La experiencia promedio de la industria es de aproximadamente 1 a 25 errores por cada 1000


2
1
líneas de código para el software entregado. El software generalmente se ha desarrollado

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!)

- La división de aplicaciones de Microsoft experimenta entre 10 y 20 defectos por cada 1000


líneas de código durante las pruebas internas y 0,5 defectos por cada 1000 líneas de código en
el producto lanzado (Moore 1992). La técnica utilizada para alcanzar este nivel es una
combinación de las técnicas de lectura de código descritas en la Sección 21.4, “Otros tipos de
prácticas de desarrollo colaborativo” y pruebas independientes.

- 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

desarrolladores para que no creen defectos en primer lugar (Weber 2003).


DATOS DUROS
522 Capítulo 22: Pruebas de desarrollador

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!

Errores al probarse a sí mismo

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

22.5 Herramientas de soporte de prueba

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.

Construcción de andamios para probar clases individuales

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

- Regrese el control inmediatamente, sin haber tomado ninguna acción.

- Pruebe los datos que se le alimentan.

- Imprima un mensaje de diagnóstico, tal vez un eco de los parámetros de entrada, o registre un mensaje

en un archivo.

- Obtenga valores de retorno de la entrada interactiva.

- Devuelve una respuesta estándar independientemente de la entrada.

- Queme el número de ciclos de reloj asignados al objeto o rutina real.

- 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

- Llame al objeto con un conjunto fijo de entradas.

- Solicite la entrada de forma interactiva y llame al objeto con ella.

- Tome argumentos de la línea de comandos (en los sistemas operativos que lo admitan)
y llame al objeto.

- Leer argumentos de un archivo y llamar al objeto.

- Ejecute conjuntos predefinidos de datos de entrada en varias llamadas al objeto.


524 Capítulo 22: Pruebas de desarrollador

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

sobre las herramientas de depuración, consulte


de ventajas. Debido a que es pequeño, puede conocer su contenido exacto y puede estar razonablemente
la Sección 23.5, “Herramientas de depuración: seguro de que el archivo en sí está libre de errores. Y debido a que lo crea específicamente para pruebas,
obvias y
puede diseñar su contenido para que cualquier error al usarlo sea evidente.
No tan obvio”.

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

(Pruebas de regresión)" en la Sección 22.6.


redirigir la salida a un archivo y usar una herramienta de comparación de archivos como diff para comparar
la nueva salida con la salida esperada que se envió a un archivo anteriormente. Si los resultados no son los
mismos, ha detectado un error de regresión.

Generadores de datos de prueba

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

decodificado solo con la contraseña correcta. El cifrado no solo cambió el archivo


superficialmente; alteró todo el contenido. Era fundamental que el programa pudiera
decodificar un archivo correctamente, porque de lo contrario el archivo se arruinaría.

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.

Aquí hay algunas lecciones de esta historia:

- Los generadores de datos aleatorios correctamente diseñados pueden generar combinaciones inusuales de

datos de prueba que no se le ocurrirían.

- 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.

Perturbadores del sistema

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:

- Llenado de memoriaDesea asegurarse de que no tiene ninguna variable no inicializada.


Algunas herramientas llenan la memoria con valores arbitrarios antes de ejecutar su
programa para que las variables no inicializadas no se establezcan en0accidentalmente. En
algunos casos, la memoria puede establecerse en un valor específico. Por ejemplo, en el
procesador x86, el valor 0xCCes el código en lenguaje máquina para una interrupción de
punto de interrupción. Si llenas la memoria con0xCCy tiene un error que hace que ejecute
algo que no debería, alcanzará un punto de interrupción en el depurador y detectará el error.

- 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.

- Falla de memoria selectivaUn controlador de memoria puede simular condiciones de poca


memoria en las que un programa podría quedarse sin memoria, fallar en una solicitud de
memoria, otorgar una cantidad arbitraria de solicitudes de memoria antes de fallar o fallar en
una cantidad arbitraria de solicitudes antes de otorgar una. Esto es especialmente útil para
probar programas complicados que funcionan con memoria asignada dinámicamente.

- Comprobación de acceso a la memoria (comprobación de límites)Los comprobadores de límites


observan las operaciones de los punteros para asegurarse de que se comporten correctamente. Tal
herramienta es útil para detectar punteros no inicializados o colgantes.

Bases de datos de errores

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

base de datos de errores, consulte la Sección 22.7, “Conservación de registros de pruebas”.


528 Capítulo 22: Pruebas de desarrollador

22.6 Mejorar sus pruebas


Los pasos para mejorar sus pruebas son similares a los pasos para mejorar cualquier otro
proceso. Tienes que saber exactamente lo que hace el proceso para que puedas variarlo
ligeramente y observar los efectos de la variación. Cuando observas un cambio que tiene un
efecto positivo, modificas el proceso para que sea un poco mejor. Las siguientes secciones
describen cómo hacer esto con las pruebas.

Planificación para la prueba

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

encontrar más información sobre la


que se le asignará tiempo, se considerará importante y será un proceso de alta calidad. La
documentación de las pruebas, planificación de pruebas también es un elemento para hacer que el proceso de pruebarepetible. Si
consulte la sección "Recursos
no puedes repetirlo, no puedes mejorarlo.
adicionales" al final del Capítulo 32.

Nueva prueba (prueba de regresión)

Suponga que ha probado un producto exhaustivamente y no ha encontrado errores. Suponga que


luego se cambia el producto en un área y desea asegurarse de que todavía pasa todas las pruebas
que hizo antes del cambio, que el cambio no introdujo ningún defecto nuevo. Las pruebas diseñadas
para asegurarse de que el software no haya dado un paso atrás, o "retrocedido", se denominan
"pruebas de regresión".

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).

Los beneficios de la automatización de pruebas incluyen lo siguiente:

- 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 mejoran sus posibilidades de detectar cualquier problema en el


momento más temprano posible, lo que tiende a minimizar el trabajo necesario para
diagnosticar y corregir el problema.

- 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

las prácticas de desarrollo, consulte


Las principales herramientas utilizadas para respaldar las pruebas automatizadas proporcionan andamiaje
la Sección 4.3, “Su ubicación en la ola

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.

22.7 Mantenimiento de registros de pruebas

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

- Descripción administrativa del defecto (la fecha reportada, la persona que lo


reportó, un título o descripción, el número de construcción, la fecha fijada)

- Descripción completa del problema

- Pasos a seguir para repetir el problema

- Solución sugerida para el problema


- Defectos relacionados

- Gravedad del problema, por ejemplo, fatal, molesto o cosmético

- Origen del defecto: requisitos, diseño, codificación o prueba

- Subclasificación de un defecto de codificación: fallado por uno, asignación incorrecta, índice de matriz

incorrecto, llamada de rutina incorrecta, etc.

- Clases y rutinas cambiadas por el arreglo.

- Número de líneas de código afectadas por el defecto

- Horas para encontrar el defecto

- Horas para arreglar el defecto

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

- Número de defectos en cada rutina, ordenados de peor a mejor rutina, posiblemente


normalizados por tamaño de rutina

- Promedio de horas de prueba por defecto encontrado

- Promedio de defectos encontrados por caso de prueba

- Promedio de horas de programación por defecto reparado

- Porcentaje de código cubierto por casos de prueba

- Número de defectos pendientes en cada clasificación de gravedad

Registros de pruebas personales

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.

Primera prueba de desarrollo

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

IEEE Std 1008-1987 (R1993), estándar para pruebas de unidades de software

IEEE Std 829-1998, estándar para documentación de prueba de software

IEEE Std 730-2002, Estándar para Planes de Garantía de Calidad de Software

cc2e.com/2210 LISTA DE VERIFICACIÓN: Casos de prueba

- ¿Cada requisito que se aplica a la clase o rutina tiene su propio caso de


prueba?

- ¿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 verificado el código en busca de patrones de flujo de datos que probablemente no


sean correctos, como definido-definido, definido-salido y definido-matado?

- ¿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 prueban valores representativos intermedios?

- ¿Se prueba la configuración normal mínima?

- ¿Se prueba la configuración normal máxima?

- ¿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?

- ¿Los casos de prueba facilitan las comprobaciones manuales?


Puntos clave 533

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.

- A la larga, la mejor manera de mejorar su proceso de prueba es regularlo,


medirlo y usar lo que aprende para mejorarlo.
Traducido del inglés al español - www.onlinedoctranslator.com
capitulo 23

depuración
cc2e.com/2361 Contenido

- 23.1 Resumen de problemas de depuración: página 535

- 23.2 Búsqueda de un defecto: página 540

- 23.3 Reparación de un defecto: página 550

- 23.4 Consideraciones psicológicas en la depuración: página 554

- 23.5 Herramientas de depuración: obvias y no tan obvias: página 556

Temas relacionados

- El panorama de la calidad del software: Capítulo 20

- Pruebas de desarrollador: Capítulo 22

- 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

código de la manera más inteligente


depuración ocupa hasta el 50 por ciento del tiempo total de desarrollo. Para muchos
posible, por definición, no es lo programadores, la depuración es la parte más difícil de la programación.
suficientemente inteligente como

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.

23.1 Descripción general de los problemas de depuración

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).

La palabra "bicho" es una palabra linda y evoca imágenes como esta:

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".

Papel de la depuración en la calidad del software

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.

Variaciones en el rendimiento de depuración

¿Por qué hablar de depuración? ¿No todos saben cómo depurar?

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:

Los tres más rápidos Los tres más lentos


programadores programadores

Tiempo medio de depuración (minutos) 5.0 14.1


Número medio de defectos no encontrados 0.7 1.7
Número medio de defectos realizados corrigiendo 3.0 7.7
defectos
Fuente: "Alguna evidencia psicológica sobre cómo las personas depuran los programas de computadora" (Gould 1975)

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

Sección 20.5, “El principio general


encontraron la mayoría de los defectos, encontraron los defectos más rápidamente y realizaron las
de la calidad del software”. modificaciones correctas con mayor frecuencia. No tiene que elegir entre calidad, costo y tiempo,
todos van de la mano.

Defectos como oportunidades

¿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:

Infórmate sobre el programa en el que estás trabajandoTienes algo que aprender


sobre el programa porque si ya lo conocieras a la perfección, no tendría ningún defecto.
Ya lo habrías corregido.

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

errores a los que es propenso


pero ese día es una oportunidad, así que aprovéchalo. Una vez que encuentre el error,
personalmente, consulteUna pregúntese cómo y por qué lo cometió. ¿Cómo pudiste haberlo encontrado más rápido?
disciplina para la ingeniería de
¿Cómo pudiste haberlo evitado? ¿El código tiene otros errores como este? ¿Puedes
software (Humphrey 1995).
corregirlos antes de que causen sus propios problemas?

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.

Aprende cómo resuelves problemas¿Su enfoque para resolver problemas de depuración le


da confianza? ¿Funciona tu enfoque? ¿Encuentras defectos rápidamente? ¿O su enfoque para la
depuración es débil? ¿Sientes angustia y frustración? ¿Adivinas al azar? ¿Necesitas mejorar?
Teniendo en cuenta la cantidad de tiempo que muchos proyectos dedican a la depuración,
definitivamente no perderá el tiempo si observa cómo depura. Tomarse el tiempo para analizar
y cambiar la forma en que depura puede ser la forma más rápida de disminuir la cantidad total
de tiempo que le lleva desarrollar un programa.

Más información sobre cómo corregir defectosAdemás de aprender cómo encontrar


defectos, puede aprender cómo solucionarlos. ¿Haces la corrección más fácil posible
aplicandoirvendajes y maquillajes especiales que cambian el síntoma pero no el
problema? ¿O hace correcciones sistémicas, exigiendo un diagnóstico preciso y
prescribiendo un tratamiento para el corazón del problema?

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!

La guía del diablo para la depuración

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.

Realizan reparaciones menores e


a depurar de manera efectiva. Tortura a los programadores haciéndoles usar estos enfoques comunes de
irracionales, y muchas veces no depuración:
deshacen elincorrecto refacción.

Encuentre el defecto adivinandoPara encontrar el defecto, disperse sentencias de impresión


—Iris Vessey
aleatoriamente a lo largo de un programa. Examine la salida para ver dónde está el defecto. Si no puede
encontrar el defecto con declaraciones de impresión, intente cambiar cosas en el programa hasta que algo
parezca funcionar. No haga una copia de seguridad de la versión original del programa y no lleve un
registro de los cambios que ha realizado. La programación es más emocionante cuando no está muy seguro
de lo que está haciendo el programa. Abastécete de refrescos y dulces porque te espera una larga noche
frente a la terminal.

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?

Depuración por superstición

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”.

Si tiene un problema con un programa que ha escrito, es su culpa. No es culpa de la


computadora, y no es culpa del compilador. El programa no hace algo diferente cada vez.
No se escribió solo; usted lo escribió, así que asuma la responsabilidad por ello.

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.

23.2 Búsqueda de un defecto

La depuración consiste en encontrar el defecto y corregirlo. Encontrar el defecto y


comprenderlo suele ser el 90 por ciento del trabajo.

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 de depuración


Estos son los pasos que sigues cuando usas el método científico clásico:

1.Recopile datos a través de experimentos repetibles.

2.Formule una hipótesis que dé cuenta de los datos relevantes.

3.Diseñar un experimento para probar o refutar la hipótesis.


23.2 Búsqueda de un defecto 541

4.Probar o refutar la hipótesis.


5.Repita según sea necesario.

El método científico tiene muchos paralelos en la depuración. Aquí hay un enfoque efectivo para
encontrar un defecto:

PUNTO CLAVE 1.Estabilizar el error.


2.Localice la fuente del error (la "falla").
una.Reúna los datos que producen el defecto.

b.Analice los datos que se han recopilado y formule una hipótesis sobre el
defecto.

C.Determine cómo probar o refutar la hipótesis, ya sea probando el


programa o examinando el código.
d.Demostrar o refutar la hipótesis utilizando el procedimiento identificado en 2(c).

3.Reparar el defecto.

4.Pruebe la corrección.

5.Busque errores similares.

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:

formateo, Fred Forma libre $5,877


Global, Gary $1,666
módulo, Mildred $10,788
muchos bucles, Tordo músico $8,889
Declaración, demandar cambiar $4,000
bucle mientras, Wendy $7,860

el error es queMuchos bucles, MavisyMódulo, Mildredestán fuera de servicio.


542 Capítulo 23: Depuración

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.

En el ejemplo de retenciones de empleados, cuando el programa se ejecuta inicialmente,Muchos bucles,


Mavisse enumera despuésMódulo, Mildred. Sin embargo, cuando el programa se ejecuta por segunda vez,
la lista está bien:

Formateo, Fred Forma libre $5,877


Global, Gary $1,666
Muchos bucles, Mavis $8,889
Módulo, Mildred $10,788
Declaración, demandar cambiar $4,000
bucle mientras, Wendy $7,860

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:

formateo, Fred forma libre $5,877


Aro de fruta, frita $5,771
Global, Gary $1,666
Muchos bucles, Mavis $8,889
Módulo, Mildred $10,788
Declaración, demandar cambiar $4,000
bucle mientras, Wendy $7,860

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.

Localice la fuente del error


Localizar la fuente del error también requiere el uso del método científico. Puede
sospechar que el defecto es el resultado de un problema específico, por ejemplo, un
error de error. Luego puede variar el parámetro que sospecha que está causando el
problema (uno por debajo del límite, en el límite y otro por encima del límite) y
determinar si su hipótesis es correcta.

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:

formateo, Fred forma libre $5,877


Aro de fruta, frita $5,771
Global, Gary $1,666
Estuche duro, Enrique $493
muchos bucles, Tordo músico $8,889
Módulo, Mildred $10,788
Declaración, demandar Cambiar $4,000
bucle mientras, Wendy $7,860

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.

Posteriormente, confirma esta hipótesis con casos de prueba adicionales.

Consejos para encontrar defectos

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.

Si está utilizando un depurador, no necesariamente tiene que eliminar fragmentos de código.


Puede establecer un punto de interrupción en la mitad del programa y verificar el defecto de
esa manera. Si su depurador le permite omitir llamadas a rutinas, elimine los sospechosos
omitiendo la ejecución de ciertas rutinas y viendo si el error aún ocurre. Por lo demás, el
proceso con un depurador es similar a aquel en el que se eliminan físicamente partes de un
programa.
23.2 Búsqueda de un defecto 547

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

errores de destino" en la Sección 24.5.


pasado contenga un nuevo defecto que una clase que ha estado libre de defectos. Vuelva a examinar las
clases y rutinas propensas a errores.

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.

Comprobar defectos comunesUse listas de verificación de calidad de código para estimular su


pensamiento sobre posibles defectos. Si está siguiendo las prácticas de inspección descritas en
la Sección 21.3, “Inspecciones formales”, tendrá su propia lista de verificación detallada de los
problemas comunes en su entorno. También puede usar las listas de verificación que aparecen
a lo largo de este libro. Consulte la "Lista de listas de control" que sigue al índice del libro.

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

una distancia beneficiosa entre usted


Por ejemplo, si estuviera explicando el problema en el ejemplo del salario, podría sonar así:
y el problema, consulte la Sección

21.1, “Resumen de las prácticas de

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.

El beneficio auxiliar de renunciar temporalmente es que reduce la ansiedad asociada con la


depuración. La aparición de la ansiedad es una clara señal de que es hora de tomar un descanso.

Depuración de fuerza bruta

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.

- Deseche la sección de código y vuelva a diseñarla/recodificarla desde cero.

- Deseche todo el programa y vuelva a diseñarlo/recodificarlo desde cero.

- Compile el código con información de depuración completa.

- Compile el código en el nivel de advertencia más exigente y corrija todas las advertencias del compilador exigente.

- Póngase un arnés de prueba de unidad y pruebe el nuevo código de forma aislada.

- Cree un conjunto de pruebas automatizado y ejecútelo toda la noche.

- Pase por un bucle grande en el depurador manualmente hasta que llegue a la condición
de error.

- Instrumente el código con declaraciones de impresión, visualización u otras declaraciones de registro.

- Compile el código con un compilador diferente.

- Compile y ejecute el programa en un entorno diferente.

- Vincule o ejecute el código en bibliotecas especiales o entornos de ejecución que generan


advertencias cuando el código se usa incorrectamente.

- Replicar la configuración completa de la máquina del usuario final.

- 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

programación de onda temprana


entornos más primitivos, un comentario o una comilla fuera de lugar pueden hacer tropezar al
frente a los de onda madura. Para compilador. Para encontrar el comentario adicional o las comillas, inserte la siguiente secuencia en
obtener más información, consulte la
su código en C, C++ y Java:
Sección 4.3, “Su ubicación en la ola

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.

23.3 Reparación de un defecto

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

Comprender el programa, no solo el problemaSi comprende el contexto en el que ocurre un


problema, es más probable que lo resuelva por completo en lugar de solo un aspecto. Un estudio
realizado con programas cortos encontró que los programadores que logran una comprensión global
del comportamiento del programa tienen más posibilidades de modificarlo con éxito que los
programadores que se enfocan en el comportamiento local, aprendiendo sobre el programa solo
cuando lo necesitan (Littman et al. 1986). Debido a que el programa en este estudio era pequeño (280
líneas), no prueba que deba tratar de comprender completamente un programa de 50,000 líneas
antes de corregir un defecto. Sugiere que debe comprender al menos el código en la vecindad de la
corrección del defecto; la "vecindad" no es unas pocas líneas, sino unos cientos.

Confirmar el diagnóstico del defectoAntes de apresurarse a reparar un defecto, asegúrese de


haber diagnosticado el problema correctamente. Tómese el tiempo para ejecutar casos de prueba
que prueben su hipótesis y refuten las hipótesis contrapuestas. Si solo probó que el error podría ser
el resultado de una de varias causas, aún no tiene suficiente evidencia para trabajar en la única
causa; descartar los demás primero.

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?

Si esto no es el colmo de la imprudencia, está cerca y es común. Apresurarse para resolver un


problema es una de las cosas que menos tiempo puede hacer. Conduce a juicios apresurados,
diagnósticos de defectos incompletos y correcciones incompletas. Las ilusiones pueden llevarte
a ver soluciones donde no las hay. La presión, a menudo autoimpuesta, fomenta soluciones
fortuitas de prueba y error y la suposición de que una solución funciona sin verificar que lo
hace.

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

Capítulo 24, "Refactorización".


un grupo de cambios es el significativo. Si tiene el código fuente original, al menos puede
comparar los archivos antiguos y nuevos y ver dónde están los cambios.

Solucione el problema, no el síntomaTambién debe solucionar el síntoma, pero el enfoque


debe estar en solucionar el problema subyacente en lugar de envolverlo en cinta adhesiva de
programación. Si no comprende completamente el problema, no está arreglando el código.
Está arreglando el síntoma y empeorando el código. Supongamos que tiene este código:

Ejemplo Java de código que debe corregirse


for (númeroReclamación = 0; NúmeroReclamación < númeroReclamaciones[cliente]; NúmeroReclamación++) {
sum[ cliente ] = sum[ cliente ] + cantidadreclamación[ númeroreclamación ];
}

Supongamos además que cuandoclientees igual45,sumaresulta estar equivocado por $3.45. Esta es la
forma incorrecta de solucionar el problema:

Ejemplo de Java de empeorar el código al "arreglarlo"


for (númeroReclamación = 0; NúmeroReclamación < númeroReclamaciones[cliente]; NúmeroReclamación++) {
sum[ cliente ] = sum[ cliente ] + cantidadreclamación[ númeroreclamación ];
CODIFICACIÓN }
HORROR

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:

Ejemplo de Java de cómo empeorar el código “arreglándolo” (continuación)


for (númeroReclamación = 0; NúmeroReclamación < númeroReclamaciones[cliente]; NúmeroReclamación++) {
sum[ cliente ] = sum[ cliente ] + cantidadreclamación[ númeroreclamación ];
CODIFICACIÓN }
HORROR

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.

23.4 Consideraciones psicológicas en la depuración


Otras lecturasPara una excelente La depuración es tan intelectualmente exigente como cualquier otra actividad de desarrollo de
discusión de los problemas
software. Tu ego te dice que tu código es bueno y no tiene ningún defecto, incluso cuando has
psicológicos en la depuración, así

como muchas otras áreas del


visto que lo tiene. Tienes que pensar con precisión, formular hipótesis, recopilar datos, analizar
desarrollo de software, consulte hipótesis y rechazarlas metódicamente, con una formalidad que no es natural para muchas
La psicología de la programación
personas. Si está compilando código y depurándolo, debe cambiar rápidamente entre el
informática
(Weinberg 1998).
pensamiento fluido y creativo que acompaña al diseño y el pensamiento rígido y crítico que
acompaña a la depuración. A medida que lee su código, debe luchar contra la familiaridad del
código y evitar ver lo que espera ver.

Cómo el “Conjunto Psicológico” Contribuye a Depurar la Ceguera


Cuando usted m que dicenúmero, ¿que ves? ¿Ves un error? ¿Ves la
ortografía de abreviatura de "Número"? La mayoría r “Número”.
probablemente, tu Este es el fenómeno de la "psicología".
ical set”—ver lo que esperas ver. ¿Qué dice este letrero?

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:

- Estudiantes aprendiendotiempolos bucles a menudo esperan que un bucle se evalúe continuamente;


es decir, esperan que el ciclo termine tan pronto como eltiempola condición se convierte en
23.4 Consideraciones psicológicas en la depuración 555

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.

3 - Un programador que involuntariamente usó tanto la variableSISTEMASy la variable


2
1
SISTEMASpensó que estaba usando una sola variable. No descubrió el problema
DATOS DUROS
hasta que el programa se ejecutó cientos de veces y se escribió un libro que
contenía los resultados erróneos (Weinberg 1998).

- Un programador mirando un código como este código:

si ( x < y )
= X;
intercambio

X = y;
y = intercambio;

a veces ve código como este código:


si ( x < y ) {
= X;
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

Cómo puede ayudar la “distancia psicológica”


Referencia cruzadaPara obtener detalles La distancia psicológica se puede definir como la facilidad con la que se pueden diferenciar dos
sobre la creación de nombres de variables
elementos. Si está mirando una larga lista de palabras y le han dicho que se trata de patos,
que no sean confusos, consulte la Secció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.

La tabla 23-1 enumera ejemplos de distancias psicológicas entre nombres de variables:

Cuadro 23-1 Ejemplos de distancia psicológica entre nombres de variables


primera variable segunda variable Distancia psicológica
detener stcppt casi invisible
cambio cambio Casi ninguno
cuenta contar Pequeña

reclamos1 reclamos2 Pequeña

producto suma Largo

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.

23.5 Herramientas de depuración: obvias y no tan obvias


Referencia cruzadaLa línea entre las Puede hacer gran parte del trabajo detallado y agotador de la depuración con las herramientas de
herramientas de prueba y depuración es
depuración que están fácilmente disponibles. La herramienta que clavará la estaca final en el corazón
borrosa. Consulte la Sección 22.5 para obtener

más información sobre herramientas de prueba


del vampiro defectuoso aún no está disponible, pero cada año trae una mejora incremental en las
y el Capítulo 30 para obtener más información capacidades disponibles.
sobre herramientas de desarrollo de software.

Comparadores de código fuente

Un comparador de código fuente como Diff es útil cuando modifica un programa en


respuesta a errores. Si realiza varios cambios y necesita eliminar algunos que no puede
recordar, un comparador puede identificar las diferencias y refrescar su memoria. Si
descubre un defecto en una nueva versión que no recuerda en una versión anterior,
puede comparar los archivos para determinar qué cambió.
23.5 Herramientas de depuración: obvias y no tan obvias 557

Mensajes de advertencia del compilador

Una de las herramientas de depuración más simples y efectivas es su propio compilador.

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

estándar del proyecto o una secuencia de comandos de compilación.

Verificación extendida de sintaxis y lógica


Puede usar herramientas adicionales para verificar su código más a fondo que su compilador. Por
ejemplo, para los programadores de C, la utilidad lint verifica minuciosamente el uso de variables no
inicializadas (escribiendo=cuando quieres decir= =) y problemas igualmente sutiles.

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

clases individuales" en la Sección 22.5.


de exorcizar los demonios de un programa propenso a errores.

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.

Los depuradores de sistemas operan a nivel de sistemas en lugar de a nivel de aplicaciones


para que no interfieran con la ejecución del programa que se está depurando. Ellos son
23.5 Herramientas de depuración: obvias y no tan obvias 559

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

prueba y error en lugar de la


no usarlos. Recomiendan usar su cerebro y evitar las herramientas de depuración por completo. Su
piratería sistemática. argumento es que las herramientas de depuración son una muleta y que los problemas se
diseño, y también esconde gente
encuentran más rápido y con mayor precisión pensando en ellos que confiando en las herramientas.
marginal apenas calificada para la
programación de precisión.
Argumentan que usted, en lugar del depurador, debe ejecutar mentalmente el programa para
—molinos harlan eliminar los defectos.

Independientemente de la evidencia empírica, el argumento básico contra los depuradores no es


válido. El hecho de que una herramienta pueda ser mal utilizada no implica que deba ser rechazada.
No evitaría tomar aspirina simplemente porque es posible una sobredosis. No evitaría cortar el
césped con una cortadora de césped eléctrica solo porque es posible cortarse. Se puede usar o
abusar de cualquier otra herramienta poderosa, al igual que un depurador.

El depurador no es un sustituto del buen pensamiento. Pero, en algunos casos, pensar


tampoco es un sustituto de un buen depurador. La combinación más efectiva es un buen
pensamiento y un buen depurador.
PUNTO CLAVE

cc2e.com/2368 LISTAS DE VERIFICACIÓN: Recordatorios de depuración

Técnicas para encontrar defectos


- Usa todos los datos disponibles para hacer tu hipótesis.

- Refinar los casos de prueba que producen el error.

- Ejercita el código en tu conjunto de pruebas unitarias.

- Utilice las herramientas disponibles.

- Reproduzca el error de varias maneras diferentes.

- Generar más datos para generar más hipótesis.

- Utilice los resultados de las pruebas negativas.

- Lluvia de ideas para posibles hipótesis.

- Mantenga un bloc de notas junto a su escritorio y haga una lista de cosas para probar.

- Limite la región sospechosa del código.

- Desconfíe de las clases y rutinas que han tenido defectos antes.

- Compruebe el código que ha cambiado recientemente.

- Expanda la región sospechosa del código.


560 Capítulo 23: Depuración

- Integrar de forma incremental.

- Compruebe si hay defectos comunes.

- Hable con otra persona sobre el problema.

- Tómese un descanso del problema.

- Establezca un tiempo máximo para una depuración rápida y sucia.

- Haz una lista de técnicas de fuerza bruta y utilízalas.

Técnicas para errores de sintaxis


- No confíe en los números de línea en los mensajes del compilador.

- No confíes en los mensajes del compilador.

- No confíes en el segundo mensaje del compilador.

- Divide y conquistaras.

- Utilice un editor dirigido por la sintaxis para encontrar comentarios y comillas fuera de
lugar.

Técnicas para reparar defectos


- Comprenda el problema antes de solucionarlo.

- Entienda el programa, no sólo el problema.


- Confirme el diagnóstico del defecto.

- Relax.

- Guarde el código fuente original.

- Solucione el problema, no el síntoma.

- Cambie el código solo por una buena razón.

- Haz un cambio a la vez.


- Comprueba tu corrección.

- Agregue una prueba unitaria que exponga el defecto.

- Busque defectos similares.

Enfoque general para la depuración


- ¿Utiliza la depuración como una oportunidad para aprender más sobre su programa,
errores, calidad del código y enfoque de resolución de problemas?

- ¿Evita el enfoque supersticioso de prueba y error para la depuración?


Recursos adicionales 561

- ¿Asumes que los errores son tu culpa?

- ¿Utiliza el método científico para estabilizar los errores intermitentes?

- ¿Utiliza el método científico para encontrar defectos?

- En lugar de utilizar el mismo enfoque cada vez, ¿utiliza varias técnicas


diferentes para encontrar defectos?

- ¿Verificas que el arreglo es correcto?

- ¿Utiliza mensajes de advertencia del compilador, perfiles de ejecución, un marco


de prueba, scaffolding y depuración interactiva?

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:

Robbins, John.Depuración de aplicaciones para Microsoft .NET y Microsoft Windows.


Redmond, WA: Microsoft Press, 2003.

McKay, Everett N. y Mike Woodring.Depuración de programas de Windows: estrategias,


herramientas y técnicas para programadores de Visual C++. Boston, MA: Addison-Wesley, 2000.
562 Capítulo 23: Depuración

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.

- Un enfoque sistemático para encontrar y corregir errores es fundamental para el éxito.


Enfoca tu depuración para que cada prueba te haga avanzar un paso. Utilice el método
científico de depuración.

- 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ó.

- Establezca la advertencia de su compilador en el nivel más exigente posible y corrija los


errores que informa. Es difícil corregir errores sutiles si ignora los obvios.

- Las herramientas de depuración son ayudas poderosas para el desarrollo de software.


Encuéntralos y úsalos, y recuerda usar tu cerebro al mismo tiempo.
capitulo 24

refactorización

cc2e.com/2436 Contenido

- 24.1 Tipos de evolución del software: página 564

- 24.2 Introducción a la refactorización: página 565

- 24.3 Refactorizaciones específicas: página 571

- 24.4 Refactorización segura: página 579

- 24.5 Estrategias de refactorización: página 582

Temas relacionados

- Consejos para corregir defectos: Sección 23.3

- Enfoque de ajuste de código: Sección 25.6

- Diseño en construcción: Capítulo 5

- Clases obreras: Capítulo 6

- Rutinas de alta calidad: Capítulo 7

- Construcción colaborativa: Capítulo 21

- Pruebas de desarrollador: Capítulo 22

- Á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

564 Capítulo 24: Refactorización

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.

24.1 Tipos de evolución del software


La evolución del software es como la evolución biológica en el sentido de que algunas mutaciones
son beneficiosas y muchas no lo son. La buena evolución del software produce código cuyo
desarrollo imita el ascenso de los monos a los neandertales a nuestro actual estado exaltado como
desarrolladores de software. Sin embargo, las fuerzas evolutivas golpean a veces un programa en
sentido contrario, llevándolo a una espiral evolutiva.

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.

Filosofía de la Evolución del Software


No hay código tan grande, Una debilidad común en los enfoques de los programadores sobre la evolución del software es
retorcido o complejo que el
que continúa como un proceso inconsciente. Si reconoce que la evolución durante el
mantenimiento no pueda

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

24.2 Introducción a la refactorización


La estrategia clave para lograr la regla cardinal de la evolución del software es la refactorización, que
Martin Fowler define como “un cambio realizado en la estructura interna del software para que sea
más fácil de entender y más económico de modificar sin cambiar su comportamiento
observable” (Fowler 1999) . La palabra "refactorización" en la programación moderna surgió del uso
original de Larry Constantine de la palabra "factorización" en la programación estructurada, que se
refería a descomponer un programa en sus partes constituyentes tanto como fuera posible (Yourdon
y Constantine 1979).

Razones para refactorizar

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.

Una forma de mejorar un sistema es aumentar su modularidad: aumentar la cantidad de rutinas


bien definidas y bien nombradas que hacen una cosa y la hacen bien. Cuando los cambios lo lleven a
revisar una sección de código, aproveche la oportunidad para verificar la modularidad de las rutinas
en esa sección. Si una rutina sería más limpia si parte de ella se convirtiera en una rutina separada,
cree una rutina separada.

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.

Las jerarquías de herencia deben modificarse en paralelo.Encontrarse creando una


subclase de una clase cada vez que crea una subclase de otra clase es un tipo especial de
modificación paralela y debe abordarse.

casolas declaraciones tienen que ser modificadas en paraleloA pesar de quecasodeclaraciones no


son intrínsecamente malas, si te encuentras haciendo modificaciones paralelas a similarescaso
declaraciones en varias partes del programa, debe preguntarse si la herencia podría ser un mejor
enfoque.

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,

“Datos globales”. Para obtener una


variables globales desde la última vez que visitó esa parte del código. Debido a que está menos familiarizado
explicación de las diferencias entre con el código que cuando lo escribió por primera vez, es posible que ahora encuentre las variables globales
datos globales y datos de clase,
lo suficientemente confusas como para estar dispuesto a desarrollar un enfoque más limpio. También puede
consulte "Datos de clase confundidos
tener una mejor idea de cómo aislar las variables globales en las rutinas de acceso y una idea más aguda del
con datos globales" en la Sección 5.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

Un código como este debería ser una advertencia para usted:

Ejemplo incorrecto en C++ de configuración y eliminación de código para una llamada de rutina

Este código de configuración es Retiro Retiro de transacciones; retiro.SetCustomerId( retiro


una advertencia. de ID de cliente.SetBalance( retiro de );
);
saldo.SetWithdrawalAmount( retiro.SetWithdrawalDate(retiroDate
cantidad de retiro );
);

ProcesarRetiro(retiro);

Este código de eliminación Identificación del cliente = retiro.GetCustomerId();


es otra advertencia. saldo = retiro.ObtenerSaldo();
cantidad de retiro = retiro.ObtenerCantidadRetiro();
fecha de retiro = retiro.GetWithdrawalDate();

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

retiro = nueva transacción de retiro (ID de cliente, saldo,


cantidad de retiro, fecha de retiro);
retiro.ProcesoRetiro(); borrar retiro;
24.2 Introducción a la refactorización 569

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:

Ejemplo de C++ de código que requiere varias llamadas a métodos


ProcesarRetiro( retiro.GetCustomerId(), retiro.GetBalance(),
retiro.GetWithdrawalAmount(), retiro.GetWithdrawalDate());

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á.

- Si la conjetura del programador sobre el requisito futuro es bastante cercana, el


programador aún no anticipará todas las complejidades del requisito futuro. Estas
complejidades socavan las suposiciones básicas de diseño del programador, lo que
significa que el trabajo de "diseño anticipado" tendrá que descartarse.

- 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.

- El código adicional de "diseño anticipado" crea una complejidad adicional, lo que


requiere pruebas adicionales, corrección de defectos adicionales, etc. El efecto general
es ralentizar el proyecto.
570 Capítulo 24: Refactorización

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).

cc2e.com/2443 LISTA DE VERIFICACIÓN: Razones para refactorizar

- El código está duplicado.

- Una rutina es demasiado larga.

- Un bucle es demasiado largo o está demasiado anidado.

- Una clase tiene poca cohesión.

- Una interfaz de clase no proporciona un nivel consistente de abstracción.

- Una lista de parámetros tiene demasiados parámetros.

- Los cambios dentro de una clase tienden a compartimentarse.

- Los cambios requieren modificaciones paralelas a múltiples clases.

- Las jerarquías de herencia deben modificarse en paralelo.

- casolas declaraciones tienen que ser modificadas en paralelo.

- 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.

- Un tipo de datos primitivo está sobrecargado.

- Una clase no hace mucho.


- Una cadena de rutinas pasa datos vagabundos.

- Un objeto intermediario no está haciendo nada.

- Una clase es demasiado íntima con otra.

- Una rutina tiene un nombre pobre.

- Los miembros de datos son públicos.

- Una subclase usa solo un pequeño porcentaje de las rutinas de sus padres.

- Los comentarios se utilizan para explicar el código difícil.

- Se utilizan variables globales.

- 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

Razones para no refactorizar

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.

24.3 Refactorizaciones específicas


En esta sección, presento un catálogo de refactorizaciones, muchas de las cuales describo resumiendo las
descripciones más detalladas presentadas enrefactorización(Fowler 1999). Sin embargo, no he intentado que
este catálogo sea exhaustivo. En cierto sentido, todos los casos de este libro que muestran un ejemplo de
"código incorrecto" y un ejemplo de "código bueno" son candidatos para convertirse en una refactorización.
En aras del espacio, me he centrado en las refactorizaciones que personalmente he encontrado más útiles.

Refactorizaciones de nivel de datos

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.

Mover una expresión en líneaReemplace una variable intermedia a la que se le asignó el


resultado de una expresión con la expresión misma.

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).

Introducir una variable intermediaAsigne una expresión a una variable


intermedia cuyo nombre resuma el propósito de la expresión.

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

const int PANTALLA = 0; const


int IMPRESORA = 1; const int
ARCHIVO = 2;

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.

Refactorizaciones a nivel de declaración

Aquí hay refactorizaciones que mejoran el uso de declaraciones individuales.

Descomponer una expresión booleanaSimplifique una expresión booleana introduciendo variables


intermedias bien nombradas que ayuden a documentar el significado de la expresión.

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

Consolidar fragmentos que están duplicados dentro de diferentes partes de un


condicionalSi tiene las mismas líneas de código repetidas al final de unmásbloque que tienes
al final delsibloque, mueva esas líneas de código para que ocurran después de todo elifthen-
elsebloquear.

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.

Reemplazar condicionales (especialmente repetidos)casosentencias) con polimorfismo Gran


parte de la lógica que solía estar contenida encasoLas declaraciones en programas estructurados
pueden, en cambio, integrarse en la jerarquía de herencia y lograrse a través de llamadas de rutina
polimórficas.

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.

Refactorizaciones a nivel de rutina

Aquí hay refactorizaciones que mejoran el código a nivel de rutina individual.

Extraer rutina/extraer métodoElimine el código en línea de una rutina y conviértalo en su


propia rutina.

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.

Sustituir un algoritmo simple por un algoritmo complejoReemplace un algoritmo complicado


con un algoritmo más simple.

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.

Eliminar un parámetroSi una rutina ya no usa un parámetro, elimínelo.


574 Capítulo 24: Refactorización

Separe las operaciones de consulta de las operaciones de modificación.Normalmente, las operaciones


de consulta no cambian el estado de un objeto. Si una operación comoObtenerTotales()cambia el estado de
un objeto, separa la funcionalidad de consulta de la funcionalidad de cambio de estado y proporciona dos
rutinas separadas.

Combina rutinas similares parametrizándolasDos rutinas similares pueden diferir solo


con respecto a un valor constante que se usa dentro de la rutina. Combine las rutinas en
una rutina y pase el valor que se usará como parámetro.

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.

Pase un objeto completo en lugar de campos específicosSi se encuentra pasando varios


valores del mismo objeto a una rutina, considere cambiar la interfaz de la rutina para que
tome todo el objeto en su lugar.

Pase campos específicos en lugar de un objeto completoSi se encuentra creando un objeto


solo para poder pasarlo a una rutina, considere modificar la rutina para que tome campos
específicos en lugar de un objeto completo.

Encapsular downcastingSi una rutina devuelve un objeto, normalmente debería devolver el


tipo de objeto más específico que conoce. Esto es especialmente aplicable a las rutinas que
devuelven iteradores, colecciones, elementos de colecciones, etc.

Refactorizaciones de implementación de clase

Aquí hay refactorizaciones que mejoran a nivel de clase.

Cambiar objetos de valor a objetos de referenciaSi se encuentra creando y manteniendo


numerosas copias de objetos grandes o complejos, cambie su uso de esos objetos para que
solo exista una copia maestra (el objeto de valor) y el resto del código use referencias a ese
objeto (objetos de referencia).

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:

- Sube una rutina a su superclase.


- Sube un campo a su superclase.

- Sube un cuerpo de constructor a su superclase.

Normalmente se realizan varios otros cambios para admitir la especialización en clases


derivadas:

- Empuje una rutina hacia abajo en sus clases derivadas.

- Empuje un campo hacia abajo en sus clases derivadas.

- Empuje un cuerpo de constructor hacia abajo en sus 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.

Refactorizaciones de interfaz de clase

Aquí hay refactorizaciones que mejoran las interfaces de clase.

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.

Eliminar un intermediarioSi la Clase A llama a la Clase B y la Clase B llama a la Clase C, a veces


funciona mejor que la Clase A llame directamente a la Clase C. La cuestión de si debe delegar a la
Clase B depende de lo que mejor mantenga la integridad de la interfaz de la Clase B.
576 Capítulo 24: Refactorización

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.

Encapsular rutinas no utilizadasSi se encuentra usando rutinariamente solo una parte de la


interfaz de una clase, cree una nueva interfaz para la clase que exponga solo las rutinas necesarias.
Asegúrese de que la nueva interfaz proporcione una abstracción coherente.

Contraer una superclase y una subclase si sus implementaciones son muy similaresSi la
subclase no proporciona mucha especialización, combínela con su superclase.

Refactorizaciones a nivel de sistema

Aquí hay refactorizaciones que mejoran el código a nivel de todo el sistema.

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

Cambiar asociación de clase unidireccional a asociación de clase bidireccionalSi


tiene dos clases que necesitan usar las características de la otra, pero solo una clase
puede saber sobre la otra clase, cambie las clases para que ambas se conozcan.

Cambiar asociación de clase bidireccional a asociación de clase unidireccionalSi tiene dos


clases que conocen las características de la otra, pero solo una clase que realmente necesita
saber acerca de la otra, cambie las clases para que una sepa acerca de la otra, pero no al revés.

Proporcione un método de fábrica en lugar de un simple constructorUtilice un método de fábrica


(rutina) cuando necesite crear objetos basados en un código de tipo o cuando desee trabajar con
objetos de referencia en lugar de objetos de valor.

Reemplace los códigos de error con excepciones o viceversaSegún su estrategia de


manejo de errores, asegúrese de que el código utilice el enfoque estándar.

cc2e.com/2450 LISTA DE VERIFICACIÓN: Resumen de refactorizaciones

Refactorizaciones de nivel de datos

- Reemplace un número mágico con una constante con nombre.

- Cambie el nombre de una variable con un nombre más claro o más informativo.

- Mover una expresión en línea.

- Reemplace una expresión con una rutina.

- Introducir una variable intermedia.

- Convierta una variable multiuso en varias variables de un solo uso.

- Utilice una variable local para fines locales en lugar de un parámetro.

- Convierta una primitiva de datos en una clase.

- Convierta un conjunto de códigos de tipo en una clase o una enumeración.

- Convierta un conjunto de códigos de tipo en una clase con subclases.

- Cambia una matriz a un objeto.

- Encapsular una colección.

- Reemplace un registro tradicional con una clase de datos.

Refactorizaciones a nivel de declaración

- Descomponer una expresión booleana.

- Mueve una expresión booleana compleja a una función booleana bien nombrada.
578 Capítulo 24: Refactorización

- Consolide fragmentos que están duplicados dentro de diferentes partes de un


condicional.

- Usardescansoodevolveren lugar de una variable de control de bucle.

- Regrese tan pronto como sepa la respuesta en lugar de asignar un valor de retorno dentro de
anidadossi-entonces-otrodeclaraciones.

- Reemplazar condicionales (especialmente repetidos)casoenunciados) con


polimorfismo.

- Cree y use objetos nulos en lugar de probar valores nulos.

Refactorizaciones a nivel de rutina


- Extraer una rutina.

- Mueva el código de una rutina en línea.

- Convierta una rutina larga en una clase.

- Sustituir un algoritmo simple por un algoritmo complejo.

- Agregar un parámetro.

- Eliminar un parámetro.

- Separe las operaciones de consulta de las operaciones de modificación.

- Combine rutinas similares parametrizándolas.


- Rutinas separadas cuyo comportamiento depende de los parámetros pasados.

- Pase un objeto completo en lugar de campos específicos.

- Pase campos específicos en lugar de un objeto completo.

- Encapsular downcasting.

Refactorizaciones de implementación de clase

- Cambiar objetos de valor a objetos de referencia.

- Cambiar objetos de referencia a objetos de valor.

- Reemplace las rutinas virtuales con la inicialización de datos.

- Cambie la rutina del miembro o la ubicación de los datos.

- Extraiga código especializado en una subclase.

- Combina código similar en una superclase.


24.4 Refactorización segura 579

Refactorizaciones de interfaz de clase

- Mover una rutina a otra clase.

- Convierte una clase en dos.

- Eliminar una clase.

- Ocultar un delegado.

- Eliminar un intermediario.

- Reemplaza herencia por delegación.

- Reemplazar delegación con herencia.

- Introduce una rutina extraña.

- Introducir una clase de extensión.

- Encapsule una variable miembro expuesta.

- RemoverEstablecer()rutinas para campos que no se pueden cambiar.

- Oculte las rutinas que no están destinadas a ser utilizadas fuera de la clase.

- Encapsule las rutinas no utilizadas.

- Contraiga una superclase y una subclase si sus implementaciones son muy


similares.

Refactorizaciones a nivel de sistema

- Cree una fuente de referencia definitiva para los datos que no puede controlar.

- Cambie la asociación de clase unidireccional a la asociación de clase bidireccional.

- Cambie la asociación de clase bidireccional a la asociación de clase unidireccional.

- Proporcione una rutina de fábrica en lugar de un simple constructor.

- Reemplace los códigos de error con excepciones o viceversa.

24.4 Refactorización segura


Abrir un sistema en La refactorización es una técnica poderosa para mejorar la calidad del código. Como todas las herramientas
funcionamiento es más como
poderosas, la refactorización puede causar más daño que bien si se usa mal. Unas pocas pautas simples pueden
abrir un cerebro humano y

reemplazar un nervio que abrir un


evitar errores de refactorización.
fregadero y reemplazar una

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.

hacer un estacionamientoCuando esté a la mitad de una refactorización, a veces encontrará


que necesita otra refactorización. A la mitad de esa refactorización, encuentra una tercera
refactorización que sería beneficiosa. Para los cambios que no se necesitan de inmediato, haga
un "estacionamiento", una lista de los cambios que le gustaría hacer en algún momento pero
que no es necesario hacer ahora.

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

errores casi tan pronto como los escriba.

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).

Ajuste su enfoque según el nivel de riesgo de la refactorizaciónAlgunas refactorizaciones son más


riesgosas que otras. Una refactorización como "Reemplazar un número mágico con una constante con
nombre" está relativamente libre de riesgos. Las refactorizaciones que implican cambios en la interfaz de
clases o rutinas, cambios en el esquema de la base de datos o cambios en las pruebas booleanas, entre
otros, tienden a ser más riesgosas. Para refactorizaciones más sencillas, puede simplificar su proceso de
refactorización para hacer más de una refactorización a la vez y simplemente volver a probar, sin pasar por
una revisión oficial.

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,

además de la verificación normal del compilador y las pruebas unitarias.


582 Capítulo 24: Refactorización

Malos tiempos para refactorizar

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

programadores que modifican código roto no están refactorizando; están hackeando.

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.

24.5 Estrategias de refactorización


El número de refactorizaciones que serían beneficiosas para cualquier programa específico es
esencialmente infinito. La refactorización está sujeta a la misma ley de rendimientos decrecientes que
otras actividades de programación y se aplica la regla 80/20. Dedique su tiempo al 20 por ciento de
las refactorizaciones que brindan el 80 por ciento del beneficio. Considere las siguientes pautas al
decidir qué refactorizaciones son las más importantes:

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

errores?" en la Sección 22.4.


Probablemente sea un módulo propenso a errores. Aunque la tendencia natural de la mayoría de las
personas es evitar estas secciones de código desafiantes, enfocarse en estas secciones para la
refactorización puede ser una de las estrategias más efectivas (Jones 2000).
24.5 Estrategias de refactorización 583

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

Desordenado mundo real

Interfaz para el mundo real desordenado

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

mente movi Carolina del Sur

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".

cc2e.com/2457 LISTA DE VERIFICACIÓN: Refactorización segura

- ¿Cada cambio es parte de una estrategia de cambio sistemático?

- ¿Guardó el código con el que comenzó antes de comenzar la refactorización?

- ¿Mantienes cada refactorización pequeña?

- ¿Estás haciendo refactorizaciones una a la vez?

- ¿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 vuelto a realizar la prueba después de cada refactorización?

- ¿Se han revisado los cambios si son complicados o si afectan el código de


misión crítica?

- ¿Ha considerado el riesgo de la refactorización específica y ha ajustado su


enfoque en consecuencia?

- ¿El cambio mejora la calidad interna del programa en lugar de


degradarla?

- ¿Ha evitado usar la refactorización como una tapadera para el código y la corrección o como una excusa

para no volver a escribir un código incorrecto?


Recursos adicionales 585

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.

- El software puede mejorar o degradarse a medida que cambia. La regla cardinal de la


evolución del software es que la calidad interna debe mejorar con la evolución del código.

- 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.

- Otra clave para el éxito de la refactorización es aprender numerosas refactorizaciones específicas.

- Una clave final para el éxito es tener una estrategia para refactorizar de manera segura. Algunos enfoques

de refactorización son mejores que otros.

- La refactorización durante el desarrollo es la mejor oportunidad que tendrá para mejorar su


programa, para hacer todos los cambios que desearía haber hecho la primera vez.
¡Aproveche estas oportunidades durante el desarrollo!
capitulo 25

Estrategias de ajuste de código

cc2e.com/2578 Contenido

- 25.1 Descripción general del rendimiento: página 588

- 25.2 Introducción al Code Tuning: página 591

- 25.3 Tipos de grasa y melaza: página 597

- 25.4 Medición: página 603


- 25.5 Iteración: página 605

- 25.6 Resumen del enfoque de ajuste de código: página 606

Temas relacionados

- Técnicas de ajuste de código: Capítulo 26

- Arquitectura de software: Sección 3.5

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

25.1 Descripción general del rendimiento

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.

Características de calidad y rendimiento


Se cometen más pecados Algunas personas miran el mundo a través de lentes color de rosa. Los programadores como
informáticos en nombre de la
tú y como yo tendemos a mirar el mundo a través de lentes de colores. Suponemos que
eficiencia (sin lograrla
necesariamente) que por cuanto mejor hagamos el código, más les gustará nuestro software a nuestros clientes.
cualquier otra razón:
incluida la estupidez ciega. Este punto de vista podría tener una dirección postal en algún lugar en la realidad, pero no tiene un número
—W.A. Wulf de calle y ciertamente no posee ningún inmueble. Los usuarios están más interesados en las características
tangibles del programa que en la calidad del código. A veces, los usuarios están interesados en el
rendimiento en bruto, pero solo cuando afecta su trabajo. Los usuarios tienden a estar más interesados en
el rendimiento del programa que en el rendimiento bruto. Entregar el software a tiempo, proporcionar una
interfaz de usuario limpia y evitar el tiempo de inactividad suele ser más importante.

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.

Ajuste de rendimiento y código


Una vez que haya elegido la eficiencia como una prioridad, ya sea que su énfasis esté en la velocidad
o en el tamaño, debe considerar varias opciones antes de elegir mejorar la velocidad o el tamaño a
nivel de código. Piense en la eficiencia desde cada uno de estos puntos de vista:
25.1 Descripción general del rendimiento 589

- Requisitos del programa

- Diseño de programa

- Diseño de clases y rutinas.

- Interacciones del sistema operativo

- Compilación de código

- Hardware

- ajuste de código

Requisitos del programa

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

la sección "Recursos adicionales" al


dificultan la escritura de un sistema de alto rendimiento. Otros hacen que sea difícil no hacerlo.
final de este capítulo.
Considere el ejemplo de un programa de adquisición de datos del mundo real para el cual el diseño
de alto nivel había identificado el rendimiento de medición como un atributo clave del producto.
Cada medición incluía tiempo para realizar una medición eléctrica, calibrar el valor, escalar el valor y
convertirlo de unidades de datos del sensor (como milivoltios) a unidades de datos de ingeniería
(como grados Celsius).

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

"Establecimiento de objetivos" en la Sección


orientada al rendimiento y luego establezca objetivos de recursos para subsistemas, características y clases
20.2. individuales. Esto ayudará de varias maneras:

- 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.

Diseño de clases y rutinas


Referencia cruzadaPara más Diseñar las partes internas de las clases y las rutinas presenta otra oportunidad de diseñar para el
información sobre datos
desempeño. Una clave de rendimiento que entra en juego en este nivel es la elección de tipos de datos y
tipos y algoritmos,
consulte la sección algoritmos, que generalmente afectan tanto el uso de la memoria del programa como la velocidad de
"Recursos adicionales" al ejecución. Este es el nivel en el que puede elegir ordenación rápida en lugar de ordenación por burbujas o
final del capítulo.
una búsqueda binaria en lugar de una búsqueda lineal.

Interacciones del sistema operativo

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

voluminosos, consulte el Capítulo 26,


del sistema operativo son lentas o pesadas. Es posible que no sepa que el programa está interactuando con
"Técnicas de ajuste de código". el sistema operativo; a veces, su compilador genera llamadas al sistema o sus bibliotecas invocan llamadas al
sistema con las que nunca soñaría. Más sobre esto más adelante.

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.

Los resultados de la optimización informados en el Capítulo 26 brindan numerosos ejemplos de


optimizaciones del compilador que producen un código más eficiente que el ajuste manual del código.
25.2 Introducción al ajuste de código 591

Hardware

A veces, la forma mejor y más barata de mejorar el rendimiento de un programa es comprar


hardware nuevo. Si está distribuyendo un programa para uso nacional de cientos de miles de
clientes, comprar hardware nuevo no es una opción realista. Pero si está desarrollando software
personalizado para algunos usuarios internos, una actualización de hardware podría ser la opción
más económica. Ahorra el costo del trabajo de rendimiento inicial. Ahorra el costo de futuros
problemas de mantenimiento causados por el trabajo de rendimiento. También mejora el
rendimiento de todos los demás programas que se ejecutan en ese hardware.

Ajuste de código

El ajuste de código es la práctica de modificar el código correcto de manera que se ejecute de


manera más eficiente, y es el tema del resto de este capítulo. "Tuning" se refiere a cambios a
pequeña escala que afectan a una sola clase, una sola rutina o, más comúnmente, unas pocas líneas
de código. “Tuning” no se refiere a cambios de diseño a gran escala u otros medios de mayor nivel
para mejorar el rendimiento.

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.

25.2 Introducción al ajuste de código


¿Cuál es el atractivo del ajuste de código? No es la forma más eficaz de mejorar el rendimiento. La
arquitectura del programa, el diseño de clases y la selección de algoritmos suelen producir mejoras más
drásticas. Tampoco es la forma más fácil de mejorar el rendimiento: es más fácil comprar hardware nuevo o
un compilador con un mejor optimizador. Y tampoco es la forma más económica de mejorar el rendimiento:
se necesita más tiempo para ajustar manualmente el código inicialmente, y el código ajustado manualmente
es más difícil de mantener más adelante.

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

Antiguos cuentos de esposas

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:

por-Círculo Código directo


Idioma Tiempo Tiempo Ahorro de tiempo Relación calidad
básico visual 8.47 3.16 63% 2,5:1
Java 12.6 3.23 74% 4:1

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

594 Capítulo 25: Estrategias de ajuste de código

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

raíz de todos los males.


situación de bosque para los árboles en la que los programadores ignoran las optimizaciones globales
—donald knuth significativas porque están demasiado ocupados con las microoptimizaciones. Estos son los principales
problemas con la optimización a medida que avanza:

- 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?

En resumen, el principal inconveniente de la optimización prematura es su falta de perspectiva. Sus víctimas


incluyen la velocidad del código final, los atributos de rendimiento que son más importantes que la velocidad
del código, la calidad del programa y, en última instancia, los usuarios del software. Si el tiempo de
desarrollo ahorrado al implementar el programa más simple se dedica a optimizar el programa en ejecución,
el resultado siempre será un programa que se ejecuta más rápido que uno desarrollado con esfuerzos de
optimización indiscriminados (Stevens 1981).

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?"

"Eso varía, pero unos diez segundos por entrada".


596 Capítulo 25: Estrategias de ajuste de código

“¡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

decir, no hasta que tenga una


el programa es pesado, hágalo rápido y pequeño. No optimice hasta que sepa que necesita hacerlo.
solución perfectamente clara y sin

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.

Optimizaciones del compilador

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

El compilador tiene diferentes fortalezas y debilidades, y algunas se adaptarán mejor a


su programa que otras.

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:

tiempo sin Tiempo con


Compilador Compilador Actuación
Idioma Optimizaciones Optimizaciones Ahorro de tiempo Relación

compilador C++ 1 2.21 1.05 52% 2:1


compilador C++ 2 2.78 1.15 59% 2,5:1
compilador C++ 3 2.43 1.25 49% 2:1
compilador C# 1.55 1.55 0% 1:1
básico visual 1.78 1.78 0% 1:1
Máquina virtual Java 1 2.77 2.77 0% 1:1
Máquina virtual Java 2 1.39 1.38 <1% 1:1
Máquina virtual Java 3 2.63 2.63 0% 1:1

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.

25.3 Tipos de grasa y melaza


En el ajuste de código, encuentra las partes de un programa que son tan lentas como la melaza en
invierno y tan grandes como Godzilla y las cambia para que sean tan rápidas como un rayo
engrasado y tan delgadas que puedan esconderse en las grietas entre los otros bytes en RAM .
Siempre debe perfilar el programa para saber con certeza qué partes son lentas y gordas, pero
algunas operaciones tienen un largo historial de pereza y obesidad, y puede comenzar
investigándolas.
598 Capítulo 25: Estrategias de ajuste de código

Fuentes comunes de ineficiencia


Aquí hay varias fuentes comunes de ineficiencia:

Operaciones de entrada/salidaUna de las fuentes más importantes de ineficiencia es la entrada/


salida (E/S) innecesaria. Si tiene la opción de trabajar con un archivo en memoria o en disco, en una
base de datos o en una red, use una estructura de datos en memoria a menos que el espacio sea
crítico.

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:

Idioma Archivo externo En memoria Ahorro de tiempo Actuación


Tiempo Tiempo de datos Relación

C++ 6.04 0.000 100% n/A

C# 12.8 0.010 100% 1000:1

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:

Idioma Archivo externo En memoria Ahorro de tiempo Actuación


Tiempo Tiempo de datos Relación

C++ 3.29 0.021 99% 150:1


C# 2.60 0.030 99% 85:1
Nota: Las pruebas de acceso secuencial se ejecutaron con 13 veces el volumen de datos de las pruebas de acceso aleatorio, por lo que
los resultados no son comparables entre los dos tipos de pruebas.

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

C++ 6.04 6.64 - 10%


C# 12.8 14.1 - 10%

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

antes de tener E/S en una parte crítica de la velocidad de un programa.

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.

Ejemplo de Java de un bucle de inicialización que provoca muchos errores de página


for ( columna = 0; columna < MAX_COLUMNAS; columna++ ) {
for ( fila = 0; fila < MAX_ROWS; fila++ ) {
tabla[fila][columna] = BlankTableElement();
}
}

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.

El programador reestructuró el ciclo de esta manera:

Ejemplo de Java de un bucle de inicialización que causa pocas fallas de página


for ( fila = 0; fila < MAX_ROWS; fila++ ) {
for ( columna = 0; columna < MAX_COLUMNAS; columna++ ) {
tabla[fila][columna] = BlankTableElement();
}
}

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.

La penalización de rendimiento específica varía significativamente. En una máquina con memoria


limitada, medí que la segunda muestra de código era unas 1000 veces más rápida que la primera
muestra de código. En máquinas con más memoria, he medido que la diferencia es tan pequeña
como un factor de 2, y no aparece en absoluto excepto para valores muy grandes de MAX_ROWSy
MAX_COLUMNAS.

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).

En el esfuerzo de ajuste de código que describí en "Cuándo ajustar" en la Sección 25.2, el


programa usó unAppTimeclase que se derivó de un disponible comercialmenteBaseTiempo
clase. (Estos nombres han sido cambiados para proteger a los culpables).AppTimeEl objeto era
el objeto más común en esta aplicación, e instanciamos decenas de miles de AppTimeobjetos.
Después de varios meses, descubrimos queBaseTiempose estaba inicializando a la hora del
sistema en su constructor. Para nuestros propósitos, la hora del sistema era irrelevante, lo que
significaba que generamos innecesariamente miles de llamadas a nivel del sistema.
Simplemente anulandoBaseTiempoconstructor e inicializando eltiempocampo a 0 en lugar de a
la hora del sistema nos dio una mejora de rendimiento tan grande como todos los demás
cambios que hicimos juntos.

Idiomas interpretadosLos lenguajes interpretados tienden a imponer penalizaciones de


rendimiento significativas porque deben procesar cada instrucción del lenguaje de
programación antes de crear y ejecutar el código de máquina. En la evaluación comparativa de
desempeño que realicé para este capítulo y el Capítulo 26, observé las relaciones aproximadas
de desempeño entre diferentes lenguajes que se describen en la Tabla 25-1.

Tabla 25-1 Tiempo de ejecución relativo de los lenguajes de programación

Idioma Tipo de idioma Tiempo de ejecución relativo a C++


C++ compilado 1:1
básico visual compilado 1:1
C# compilado 1:1
Java Código de bytes 1,5:1
PHP Interpretado > 100:1
Pitón Interpretado > 100:1

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.

Costos de desempeño relativos de operaciones comunes


Aunque no puede contar con que algunas operaciones sean más costosas que otras sin
medirlas, ciertas operaciones tienden a ser más costosas. Cuando busque la melaza en su
programa, use la Tabla 25-2 para ayudar a hacer algunas conjeturas iniciales sobre las
partes difíciles de su programa.

Tabla 25-2 Costos de Operaciones Comunes

Tiempo relativo consumido


Operación Ejemplo C++ Java
Línea de base (asignación de enteros) yo = j 1 1
Llamadas de rutina

Rutina de llamada sin parámetros foo() 1 n/A

Llamar a rutina privada sin esto.foo() 1 0.5


parámetros
Llamar a rutina privada con 1 esto.foo( yo ) 1.5 0.5
parámetro

Llamar a rutina privada con 2 esto.foo( i, j ) 2 0.5


parámetros
Llamada de rutina de objeto bar.foo() 2 1
Llamada de rutina derivada barraderivada.foo() 2 1
Llamada de rutina polimórfica abstractBar.foo() 2.5 2
Referencias de objetos

Desreferencia de objeto de nivel 1 i = obj.num 1 1


Desreferencia de objeto de nivel 2 i = obj1.obj2. número i 1 1
Cada desreferencia adicional = obj1.obj2.obj3... no no
mensurable mensurable
602 Capítulo 25: Estrategias de ajuste de código

Tabla 25-2 Costos de Operaciones Comunes

Tiempo relativo consumido

Operación Ejemplo C++ Java


Operaciones con enteros

Asignación de enteros (local) yo = j 1 1


Asignación de enteros (heredada) yo = j 1 1
Suma de enteros yo = j + k 1 1
resta de enteros yo = j - k 1 1
multiplicación de enteros yo = j * k 1 1
División entera yo = j \ k 5 1.5
Operaciones de punto
flotante Asignación de punto x=y 1 1
flotante Suma de punto flotante x=y+z 1 1
Resta de punto flotante x=y-z 1 1
Multiplicación de punto flotante x=y*z 1 1
División de punto flotante x=y/z 4 1
Funciones trascendentales Raíz

cuadrada de coma flotante Seno x = sqrt( y ) 15 4


de coma flotante x = sen( y ) 25 20
Logaritmo de punto flotante x = logaritmo( y ) 25 20
Punto flotantemiy x = exp( y ) 50 20
arreglos

Acceda a una matriz de enteros con yo = un[ 5 ] 1 1


subíndice constante

Acceda a una matriz de enteros con subíndice yo = un[ j ] 1 1


variable

Acceda a una matriz de enteros yo = un[ 3, 5 ] 1 1


bidimensional con subíndices constantes

Acceda a una matriz de enteros yo = un[ j, k ] 1 1


bidimensional con subíndices variables

Acceda a una matriz de punto flotante x = z[ 5 ] 1 1


con subíndice constante

Acceda a una matriz de punto flotante x = z[ j ] 1 1


con subíndice de variable entera

Acceda a una matriz bidimensional x = z[ 3, 5 ] 1 1


de coma flotante con subíndices
constantes
Acceda a una matriz bidimensional de x = z[ j, k ] 1 1
coma flotante con subíndices de
variables enteras
Nota: Las medidas de esta tabla son muy sensibles al entorno de la máquina local, las optimizaciones del compilador y el
código generado por compiladores específicos. Las medidas entre C++ y Java no son directamente comparables.
25.4 Medición 603

El desempeño relativo de estas operaciones ha cambiado significativamente desde la primera edición de


Código completo, por lo que si se está acercando al ajuste de código con ideas de hace 10 años sobre el
rendimiento, es posible que deba actualizar su forma de pensar.

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

al convertir a notación de puntero, razoné, podría incrementar un puntero y reemplazar 10,000


costosas multiplicaciones con 10,000 operaciones de incremento relativamente baratas.
Cuidadosamente convertí el código a notación de puntero y obtuve esto:

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.

Las medidas deben ser precisas


Referencia cruzadaPara una discusión sobre las Las mediciones de rendimiento deben ser precisas. No es preciso cronometrar su programa con un
herramientas de creación de perfiles, consulte
cronómetro o contando “un elefante, dos elefantes, tres elefantes”. Las herramientas de creación de perfiles
"Ajuste de código" en la Sección 30.3.

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.

Aunque la mayoría de las optimizaciones individuales fueron pequeñas, acumulativamente fueron


significativas. A juzgar por las mejoras porcentuales, tres o incluso cuatro optimizaciones no habrían
alcanzado mi objetivo de rendimiento. Pero la combinación final fue efectiva. La moraleja de la
historia es que si profundizas lo suficiente, puedes obtener algunas ganancias sorprendentes.

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:

Referencia cruzadaLas técnicas


Mejoramiento Tiempo de referencia Mejora
enumeradas en esta tabla se

describen en el Capítulo 26, Implementar inicialmente: sencillo 21:40 —


"Técnicas de ajuste de código".
Convertir de campos de bits a matrices 7:30 sesenta y cinco%

Desenrollar hacia adentroporbucle Quitar 6:00 20%


la permutación final Combinar dos 5:24 10%
variables 5:06 5%
Utilice una identidad lógica para combinar 4:30 12%
los dos primeros pasos del algoritmo DES

Haga que dos variables compartan la misma 3:36 20%


memoria para reducir el traslado de datos en el
bucle interno

Haga que dos variables compartan la misma 3:09 13%


memoria para reducir el traslado de datos en el
bucle externo

Despliegue todos los bucles y use subíndices de 1:36 49%


matriz literal
606 Capítulo 25: Estrategias de ajuste de código

Elimine las llamadas de rutina y ponga todo el 0:45 53%


código en línea

Reescribir toda la rutina en 0:22 51%


ensamblador
Final 0:22 98%
Nota: El progreso constante de las optimizaciones en esta tabla no implica que todas las optimizaciones funcionen. No he mostrado
todas las cosas que probé que duplicaron el tiempo de ejecución. Al menos dos tercios de las optimizaciones que probé no
funcionaron.

25.6 Resumen del enfoque de ajuste de código


Debe seguir los siguientes pasos al considerar si el ajuste del código puede ayudarlo a
mejorar el rendimiento de un programa:

1.Desarrolle el software utilizando un código bien diseñado que sea fácil de entender y
modificar.

2.Si el rendimiento es pobre,

una.Guarde una versión de trabajo del código para que pueda volver al "último estado
bueno conocido".

b.Mida el sistema para encontrar puntos calientes.

C.Determine si el bajo rendimiento proviene de un diseño, tipos de datos o


algoritmos inadecuados y si el ajuste del código es apropiado. Si el ajuste del
código no es adecuado, vuelva al paso 1.

d.Sintonice el cuello de botella identificado en el paso (c).

mi.Mida cada mejora una a la vez.


F.Si una mejora no mejora el código, vuelva al código guardado en el paso (a).
(Normalmente, más de la mitad de las afinaciones intentadas producirán solo
una mejora insignificante en el rendimiento o degradarán el rendimiento).

3.Repita desde el paso 2.

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.

cc2e.com/2592 Recién llegado, Joseph M. "Optimización: su peor enemigo". mayo de 2000,www.platija.com/


optimización.htm. Newcomer es un programador de sistemas experimentado que describe
los diversos peligros de las estrategias de optimización ineficaces en detalle gráfico.

Algoritmos y tipos de datos


cc2e.com/2599 Knuth, Donald.El arte de la programación informática, vol. 1,Algoritmos Fundamentales, 3d
ed. Reading, MA: Addison-Wesley, 1997.

Knuth, Donald.El arte de la programación informática, vol. 2,Algoritmos Semiméricos, 3d ed.


Reading, MA: Addison-Wesley, 1997.

Knuth, Donald.El arte de la programación informática, vol. 3,Ordenar y buscar, 2ª ed.


Reading, MA: Addison-Wesley, 1998.

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.

cc2e.com/2506 LISTA DE VERIFICACIÓN: Estrategias de ajuste de código

Desempeño general del programa


- ¿Ha considerado mejorar el rendimiento cambiando los requisitos del
programa?

- ¿Ha considerado mejorar el rendimiento modificando el diseño del


programa?

- ¿Ha considerado mejorar el rendimiento modificando el diseño de la


clase?
608 Capítulo 25: Estrategias de ajuste de código

- ¿Ha considerado mejorar el rendimiento evitando las interacciones del


sistema operativo?

- ¿Ha considerado mejorar el rendimiento evitando la E/S?


- ¿Ha considerado mejorar el rendimiento mediante el uso de un lenguaje
compilado en lugar de un lenguaje interpretado?

- ¿Ha considerado mejorar el rendimiento mediante el uso de optimizaciones


del compilador?

- ¿Ha considerado mejorar el rendimiento cambiando a otro


hardware?
- ¿Ha considerado el ajuste de código solo como último recurso?

Enfoque de ajuste de código


- ¿Es su programa completamente correcto antes de comenzar a ajustar el código?

- ¿Ha medido los cuellos de botella de rendimiento antes de comenzar a ajustar el código?

- ¿Ha medido el efecto de cada cambio de ajuste de código?

- ¿Ha anulado los cambios de ajuste de código que no produjeron


la mejora prevista?
- ¿Ha intentado más de un cambio para mejorar el rendimiento de cada cuello de
botella, es decir,iterado?

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 medición cuantitativa es la clave para maximizar el rendimiento. Es necesario encontrar las


áreas en las que las mejoras de rendimiento realmente contarán, y se necesita nuevamente
para verificar que las optimizaciones mejoren en lugar de degradar el software.

- 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.

- La mejor manera de prepararse para el trabajo de rendimiento durante la codificación inicial es


escribir un código limpio que sea fácil de entender y modificar.
capitulo 26

Técnicas de ajuste de código


cc2e.com/2665 Contenido

- 26.1 Lógica: página 610

- 26.2 Bucles: página 616

- 26.3 Transformaciones de datos: página 624

- 26.4 Expresiones: página 630

- 26.5 Rutinas: página 639

- 26.6 Grabación en un lenguaje de bajo nivel: página 640

- 26.7 Cuanto más cambian las cosas, más permanecen igual: página 643

Temas relacionados

- Estrategias de ajuste de código: Capítulo 25

- 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

verdadero por definición. Si los cambios no degradaran la estructura interna, no los


consideraríamos optimizaciones; los usaríamos por defecto y los consideraríamos como una
práctica de codificación estándar.

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

de construcción del diseño: heurísticas”.


pronto verá, el concepto de "reglas generales" se aplica mal al ajuste de código. La única regla
general confiable es medir el efecto de cada afinación en su entorno. Por lo tanto, este capítulo
presenta un catálogo de "cosas para probar", muchas de las cuales no funcionarán en su entorno,
pero algunas funcionarán muy bien.

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

sentencias, consulte los capítulos 14 a 19.

Deja de hacer pruebas cuando sepas la respuesta

Supongamos que tiene una declaración como

si ( 5 < x ) y ( x < 10 ) entonces ...

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

evalúan las expresiones booleanas" en la


pronto como conoce la respuesta. La evaluación de cortocircuito es parte de los operadores estándar de C++
Sección 19.1. y de los operadores "condicionales" de Java.

Si su idioma no admite la evaluación de cortocircuitos de forma nativa, debe evitar usaryyo,


agregando lógica en su lugar. Con la evaluación de cortocircuito, el código anterior cambia a
esto:

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

Ejemplo de C++ de no detenerse después de saber la respuesta


entradaEncontradonegativo = falso; for ( i =
0; i < contar; i++ ) {
si ( entrada [ i ] < 0 ) {
entrada negativa encontrada = verdadero;

}
}

Un mejor enfoque sería dejar de escanear tan pronto como encuentre un valor negativo.
Cualquiera de estos enfoques resolvería el problema:

- Agrega undescansodeclaración después de laentrada negativa encontrada = verdaderolínea.

- Si tu idioma no tienedescanso, emular undescansocon unirque va a la primera


declaración después del bucle.

- Cambiar elporbucle a untiempobucle, y compruebe si hayentrada negativa encontradaasí como para


incrementar el contador de bucle pasadocontar.

- Cambiar elporbucle a untiempobucle, coloque un valor centinela en el primer elemento de la matriz


después de la última entrada de valor, y simplemente busque un valor negativo en eltiempoprueba.
Después de que termine el ciclo, vea si la posición del primer valor encontrado está en la matriz o una
más allá del final. Los centinelas se analizan con más detalle más adelante en el capítulo.

Estos son los resultados de usar eldescansopalabra clave en C++ y Java:

Idioma Tiempo exacto Tiempo sintonizado con código Ahorro de tiempo

C++ 4.27 3.68 14%


Java 4.85 3.46 29%

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

Ordenar pruebas por frecuencia

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:

Ejemplo de Visual Basic de una prueba lógica mal ordenada


Seleccione carácter de entrada
Caso "+", "="
ProcessMathSymbol( carácter de entrada )
Caso "0" a "9"
ProcessDigit( inputCharacter Case ",", ".",)
":", ";", "!", "?"
ProcessPuntuation( inputCharacter Case " " )

ProcessSpace(inputCharacter Case "A" )


a "Z", "a" a "z"
ProcesoAlfa( carácter de entrada )
caso más
Error de proceso ( carácter de entrada )
Finalizar Seleccionar

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:

Ejemplo de Visual Basic de una prueba lógica bien ordenada


Seleccionar carácter de entrada
Caso "A" a "Z", "a" a "z"
ProcesoAlfa( carácter de entrada )
Caso " "
EspacioProceso( carácter de entrada )
Caso ",", ".", ":", ";", "!", "?"
ProcessPuntuation( inputCharacter Case "0" To )
"9"
ProcessDigit(inputCharacter) Caso "+", "="

ProcessMathSymbol( inputCharacter Case )


Else
ProcessError(inputCharacter) End Select
26.1 Lógica 613

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:

Idioma Tiempo exacto Tiempo sintonizado con código Ahorro de tiempo

C# 0.220 0.260 - 18%


Java 2.56 2.56 0%
básico visual 0.280 0.260 7%
Nota: Comparado con una combinación de entrada de 78 por ciento de caracteres alfabéticos, 17 por ciento de espacios y 5 por ciento de símbolos
de puntuación.

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:

Idioma Tiempo exacto Tiempo sintonizado con código Ahorro de tiempo

C# 0.630 0.330 48%


Java 0.922 0.460 50%
básico visual 1.36 1.00 26%

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

Comparar el rendimiento de estructuras lógicas similares

La prueba descrita anteriormente podría realizarse utilizando uncasodeclaración osi-entoncess.


Dependiendo del entorno, cualquier enfoque podría funcionar mejor. Aquí están los datos de las dos
tablas anteriores reformateadas para presentar los tiempos "ajustados por código" comparandosi-
entonces-otroycasoactuación:

Idioma caso si-entonces-otro Ahorro de tiempo Relación calidad


C# 0.260 0.330 - 27% 1:1
Java 2.56 0.460 82% 6:1
básico visual 0.260 1.00 - 258% 1:4

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.

Búsquedas de tablas sustitutas para expresiones complicadas


Referencia cruzadaPara obtener detalles En algunas circunstancias, una búsqueda en una tabla puede ser más rápida que atravesar
sobre el uso de búsquedas en tablas para
una cadena lógica complicada. El objetivo de una cadena complicada suele ser categorizar algo
reemplazar la lógica complicada, consulte

el Capítulo 18, "Métodos controlados por


y luego realizar una acción basada en su categoría. Como ejemplo abstracto, suponga que
tablas". desea asignar un número de categoría a algo en función de cuál de los tres grupos: GruposA,B
, yC-cae en:

A 1 B
1 2
1
2 2
0 3
C
26.1 Lógica 615

Esta complicada cadena lógica asigna los números de categoría:

Ejemplo en C++ de una cadena lógica complicada


si ( ( un && !c ) || ( un && b && c ) ) {
categoría = 1;
}
de lo contrario si ( ( b && !a ) || ( a && c && !b ) ) {
categoría = 2;
}
si no ( c && !a && !b ) {
categoría = 3;
}
más {
categoría = 0;
}

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:

Código sintonizado Tiempo

Idioma Tiempo exacto Tiempo Ahorros Relación calidad


C++ 5.04 3.39 33% 1,5:1
básico visual 5.21 2.60 50% 2:1

Usar evaluación perezosa

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:

Ejemplo en C++ de un bucle conmutado


for ( i = 0; i < contar; i++ ) {
if (sumaTipo == SUMTYPE_NET) {
netSum = netSum + cantidad[ i ];
}
más {
sumabruta = sumabruta + monto[ i ];
}
}

En este código, la pruebasi (sumaTipo == SUMTYPE_NET)se repite en cada iteración, aunque


será el mismo cada vez que se complete el ciclo. Puede reescribir el código para una ganancia
de velocidad de esta manera:

Ejemplo en C++ de un bucle no conmutado


if (sumaTipo == SUMTYPE_NET) {
for ( i = 0; i < contar; i++ ) {
CODIFICACIÓN netSum = netSum + cantidad[ i ];
HORROR
}
}
más {
for ( i = 0; i < contar; i++ ) {
sumabruta = sumabruta + monto[ i ];
}
}
26.2 Bucles 617

NotaEste fragmento de código viola varias reglas de buena programación. La legibilidad y el


mantenimiento suelen ser más importantes que la velocidad de ejecución o el tamaño, pero en este
capítulo el tema es el rendimiento, y eso implica una compensación con los otros objetivos. Como en
el último capítulo, verá ejemplos de prácticas de codificación aquí que no se recomiendan en otras
partes de este libro.

Esto es bueno para ahorrar aproximadamente un 20 por ciento de tiempo:

Idioma Tiempo exacto Tiempo sintonizado con código Ahorro de tiempo

C++ 2.81 2.27 19%


Java 3.97 3.12 21%
básico visual 2.78 2.77 <1%
Pitón 8.14 5.87 28%

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:

Ejemplo de Visual Basic de bucles separados que podrían estar atascados


For i = 0 to employeeCount - 1
nombreEmpleado( i ) = ""
Siguiente
...
For i = 0 to employeeCount - 1
empleadoEarnings( i ) = 0
Siguiente

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

Ejemplo de Visual Basic de un bucle atascado


For i = 0 to employeeCount - 1
nombreEmpleado( i ) = ""
GananciasEmpleado( i ) = 0
Siguiente

Aquí están los ahorros:

Idioma Tiempo exacto Tiempo sintonizado con código Ahorro de tiempo

C++ 3.68 2.65 28%


PHP 3.97 2.42 32%
básico visual 3.75 3.56 4%
Nota: Benchmarked para el caso en el quenúmero de empleadoses igual a 100

Como antes, los resultados varían significativamente entre idiomas.

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:

Ejemplo Java de un bucle que se puede desenrollar


Normalmente, probablemente usaría yo = 0;
unporbucle para un trabajo como mientras ( yo < cuenta ) {
este, pero para optimizar, tendría que un[ yo ] = yo;
convertir a untiempocírculo. Para yo = yo + 1;
mayor claridad, untiempobucle se }
muestra aquí.

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

Ejemplo Java de un bucle que se ha desenrollado una vez


yo = 0;
mientras ( yo < contar - 1 ) {
CODIFICACIÓN un[ yo ] = yo;
HORROR
un[ yo + 1 ] = yo + 1; yo =
yo + 2;
}

Estas líneas recogen el caso si (i == contar - 1) {


que podría pasar a[ cuenta - 1 ] = cuenta - 1;
desapercibido si el ciclo fuera }
de dos en dos en lugar de uno.

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.

Estos son los resultados de desenrollar el bucle:

Idioma Tiempo exacto Tiempo sintonizado con código Ahorro de tiempo

C++ 1.75 1.15 34%


Java 1.01 0.581 43%
PHP 5.33 4.49 dieciséis%

Pitón 2.51 3.21 - 27%


Nota: Benchmarked para el caso en el quecontares igual a 100

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?

Ejemplo de Java de un bucle que se ha desenrollado dos veces


yo = 0;
mientras ( yo < contar - 2 ) {
CODIFICACIÓN un[ yo ] = yo;
HORROR
a[ yo + 1 ] = yo+1; a[ yo
+ 2 ] = yo+2; yo = yo + 3;

}
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;
}

Estos son los resultados de desenrollar el bucle por segunda vez:

Doble desenrollado
Idioma Tiempo exacto Tiempo Ahorro de tiempo

C++ 1.75 1.01 42%


Java 1.01 0.581 43%
PHP 5.33 3.70 31%
Pitón 2.51 2.79 - 12%
Nota: Comparado para el caso en el que el recuento es igual a 100.

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.

Minimizar el trabajo dentro de los bucles

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í:

Ejemplo en C++ de una expresión de puntero complicada dentro de un bucle


for ( i = 0; i < rateCount; i++ ) {
netRate[ i ] = baseRate[ i ] * tarifas->descuentos->factores->neto;
}

En este caso, asignar la expresión de puntero complicado a una variable bien nombrada
mejora la legibilidad y, a menudo, mejora el rendimiento.

Ejemplo de C++ de simplificación de una expresión de puntero complicada


cantidadDescuento = tarifas->descuentos->factores->neto; for ( i = 0; i
< rateCount; i++ ) {
netRate[ i ] = baseRate[ i ] * cantidadDescuento;
}
26.2 Bucles 621

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:

Idioma Tiempo exacto Tiempo sintonizado con código Ahorro de tiempo

C++ 3.69 2.97 19%


C# 2.27 1.97 13%
Java 4.13 2.35 43%
Nota: Benchmarked para el caso en el querateCountes igual a 100

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:

Ejemplo de C# de pruebas compuestas en un bucle de búsqueda


encontrado = FALSO;
yo = 0;
Aquí está la prueba compuesta. while ( ( ! encontrado ) && ( i < contar ) ) {
if ( elemento [ i ] == valor de prueba ) {
encontrado = VERDADERO;

}
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:

Ejemplo de C# del uso de un valor Sentinel para acelerar un bucle


// establece el valor centinela, preservando el valor original initialValue =
item[ count ];
Recuerde dejar espacio artículo[ cuenta ] = valor de prueba;
para el valor centinela al
final de la matriz. yo = 0;
while ( elemento [ i ] != valor de prueba ) {
yo++;
}

// comprueba si se encontró el valor


if ( i < count ) {
...

Cuandoartículoes una matriz de enteros, los ahorros pueden ser espectaculares:

Código sintonizado Tiempo

Idioma Tiempo exacto Tiempo Ahorros Relación calidad


C# 0.771 0.590 23% 1,3:1
Java 1.63 0.912 44% 2:1
básico visual 1.34 0.470 sesenta y cinco% 3:1
Nota: La búsqueda es de una matriz de 100 elementos de enteros.

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:

Idioma Tiempo exacto Tiempo sintonizado con código Ahorro de tiempo

C# 1.351 1.021 24%


Java 1.923 1.282 33%
básico visual 1.752 1.011 42%
Nota: La búsqueda es de una matriz de 100 elementos de números de coma flotante de 4 bytes.

Como es habitual, los resultados varían significativamente.


26.2 Bucles 623

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.

Poner el circuito más ocupado en el interior


Cuando tenga bucles anidados, piense qué bucle quiere en el exterior y cuál quiere
en el interior. El siguiente es un ejemplo de un bucle anidado que se puede
mejorar:

Ejemplo de Java de un bucle anidado que se puede mejorar


para ( columna = 0; columna < 100; columna++ ) {
para (fila = 0; fila < 5; fila++) {
suma = suma + tabla[fila][columna];
}
}

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:

Idioma Tiempo exacto Tiempo sintonizado con código Ahorro de tiempo

C++ 4.75 3.19 33%


Java 5.39 3.56 34%
PHP 4.16 3.65 12%
Pitón 3.48 3.33 4%

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

624 Capítulo 26: Técnicas de ajuste de código

Ejemplo de Visual Basic de multiplicación de un índice de bucle


For i = 0 to saleCount - 1
comisión( i ) = (i + 1) * ingresos * comisión base * descuento Siguiente

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.

Ejemplo de Visual Basic de sumar en lugar de multiplicar


comisiónincremental = ingresos * comisiónbase * descuentocomisiónacumulativa
= comisiónincremental
For i = 0 to saleCount - 1
comisión( i ) = comisión acumulada
comisiónacumulativa = Comisiónacumulativa + Comisiónincremental Siguiente

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:

Idioma Tiempo exacto Tiempo sintonizado con código Ahorro de tiempo

C++ 4.33 3.80 12%


básico visual 3.54 1.80 49%
Nota: Benchmark realizado conventaCuentaes igual a 20. Todas las variables calculadas son de coma flotante.

26.3 Transformaciones de datos


Los cambios en los tipos de datos pueden ser una ayuda poderosa para reducir el tamaño del programa y mejorar la
velocidad de ejecución. El diseño de la estructura de datos está fuera del alcance de este libro, pero los cambios
modestos en la implementación de un tipo de datos específico también pueden mejorar el rendimiento. Aquí hay
algunas maneras de ajustar sus tipos de datos.
26.3 Transformaciones de datos 625

Use números enteros en lugar de números de punto flotante


Referencia cruzadaPara obtener detalles La suma y la multiplicación de enteros tienden a ser más rápidas que las de punto flotante. Cambiar un
sobre el uso de números enteros y punto
índice de bucle de un punto flotante a un número entero, por ejemplo, puede ahorrar tiempo:
flotante, consulte el Capítulo 12, "Tipos de

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:

Código sintonizado Tiempo

Idioma Tiempo exacto Tiempo Ahorros Relación calidad


C++ 2.80 0.801 71% 3,5:1
PHP 5.01 4.65 7% 1:1
básico visual 6.84 0.280 96% 25:1

Utilice la menor cantidad posible de dimensiones de matriz

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:

Ejemplo de Java de una inicialización de matriz bidimensional estándar


for ( fila = 0; fila < numRows; fila++ ) {
for (columna = 0; columna <numColumnas; columna++) {
matriz[fila][columna] = 0;
}
}
626 Capítulo 26: Técnicas de ajuste de código

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:

Ejemplo de Java de una representación unidimensional de una matriz


for ( entrada = 0; entrada < numRows * numColumns; entrada++ ) {
matriz[ entrada ] = 0;
}

Y aquí hay un resumen de los resultados, con la adición de resultados comparables en varios
otros idiomas:

Código sintonizado Tiempo

Idioma Tiempo exacto Tiempo Ahorros Relación calidad


C++ 8.75 7.82 11% 1:1
C# 3.28 2.99 9% 1:1
Java 7.78 4.14 47% 2:1
PHP 6.24 4.10 34% 1,5:1
Pitón 3.31 2.23 32% 1,5:1
básico visual 9.43 3.22 66% 3:1
Nota: Los tiempos para Python y PHP no son directamente comparables con los tiempos para los otros lenguajes porque se
ejecutaron menos del 1% de iteraciones que los otros lenguajes.

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.

Minimizar referencias de matrices

Además de minimizar los accesos a arreglos de dimensiones dobles o triples, a menudo es


ventajoso minimizar los accesos a los arreglos, punto. Un bucle que usa repetidamente un
elemento de una matriz es un buen candidato para la aplicación de esta técnica. Aquí hay un
ejemplo de un acceso de matriz innecesario:

Ejemplo de C++ de hacer referencia innecesariamente a una matriz dentro de un bucle


for ( tipodescuento = 0; tipodescuento < tipoCuenta; tipodescuento++ ) {
for ( niveldescuento = 0; niveldescuento < cuentanivel; niveldescuento++ ) {
tarifa[ niveldescuento ] = tarifa[ niveldescuento ] * descuento[ tipodescuento ];
}
}
26.3 Transformaciones de datos 627

la referencia adescuento[ TipoDescuento ]no cambia cuandonivel de descuentoCambios en el


bucle interior. En consecuencia, puede moverlo fuera del ciclo interno para que solo tenga un
acceso a la matriz por ejecución del ciclo externo en lugar de uno para cada ejecución del ciclo
interno. El siguiente ejemplo muestra el código revisado.

Ejemplo de C++ de mover una referencia de matriz fuera de un bucle


for ( tipodescuento = 0; tipodescuento < tipoCuenta; tipodescuento++ ) {
esteDescuento = descuento[ tipodescuento ];
for ( niveldescuento = 0; niveldescuento < cuentanivel; niveldescuento++ ) {
tasa[ niveldescuento ] = tasa[ niveldescuento ] * esteDescuento;
}
}

Aquí están los resultados:

Idioma Tiempo exacto Tiempo sintonizado con código Ahorro de tiempo

C++ 32.1 34.5 - 7%


C# 18.3 17.0 7%
básico visual 23.2 18.4 20%
Nota: Los tiempos de referencia se calcularon para el caso en quetypeCountes igual a 10 ylevelCountes igual a 100.

Como es habitual, los resultados varían significativamente de un compilador a otro.

Usar índices complementarios


Usar un índice complementario significa agregar datos relacionados que hacen que el acceso a un tipo de
datos sea más eficiente. Puede agregar los datos relacionados al tipo de datos principal o puede
almacenarlos en una estructura paralela.

Índice de longitud de cadena

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

Estructura de índice paralelo e independiente

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.

Usar almacenamiento en caché

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

duplicó aproximadamente la velocidad de visualización.

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í:

Ejemplo Java de una rutina que favorece el almacenamiento en caché


doble hipotenusa(
doble lado a,
doble lado B
){
return Matemáticas.sqrt( (ladoA * ladoA ) + (ladoB * ladoB ) );
}

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

Ejemplo Java de almacenamiento en caché para evitar un cálculo costoso


Hipotenusa privada doble cacheada = 0; privado
doble cachedSideA = 0; privado doble
cachedSideB = 0;

publico doble Hipotenusa(


doble lado a,
doble lado B
){

// verifique si el triángulo ya está en el caché if ( ( sideA == cachedSideA ) && ( sideB


== cachedSideB ) ) {
return cachedHypotenuse;
}

// calcula la nueva hipotenusa y la almacena en caché


hipotenusa cached = Math.sqrt( (ladoA * ladoA ) + (ladoB * ladoB ) ); cachedSideA
= lado a;
cachedSideB = lado B;

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:

Código sintonizado Tiempo

Idioma Tiempo exacto Tiempo Ahorros Relación calidad


C++ 4.06 1.05 74% 4:1
Java 2.54 1.40 45% 2:1
Pitón 8.16 4.17 49% 2:1
básico visual 24.0 12.9 47% 2:1
Nota: Los resultados que se muestran asumen que la memoria caché se golpea dos veces por cada vez que se configura.

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

tiende a ser propenso a errores.


630 Capítulo 26: Técnicas de ajuste de código

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.

Explotar identidades algebraicas

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)

Si elige la segunda expresión en lugar de la primera, puede guardar unanooperación.

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:

Código sintonizado Tiempo

Idioma Tiempo exacto Tiempo Ahorros Relación calidad


C++ 7.43 0.010 99,9% 750:1
básico visual 4.59 0.220 95% 20:1
Pitón 4.21 0.401 90% 10:1

Usar reducción de fuerza


Como se mencionó anteriormente, la reducción de la fuerza significa reemplazar una operación costosa
por una más económica. Aquí hay algunas sustituciones posibles:

- Reemplace la multiplicación con la suma.

- Reemplace la exponenciación con la multiplicación.

- Reemplace las rutinas trigonométricas con sus identidades trigonométricas.

- Reemplazarlargo largoenteros conlargos oEn ts (pero observe los problemas de rendimiento asociados

con el uso de enteros de longitud nativa frente a enteros de longitud no nativa)

- 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í:

Ejemplo de Visual Basic para evaluar un polinomio


valor = coeficiente( 0 ) Para
potencia = 1 A pedido
valor = valor + coeficiente( potencia ) * x̂ potencia Siguiente

Si está pensando en la reducción de la fuerza, mirará al operador de exponenciación con


ojo ictérico. Una solución sería reemplazar la exponenciación con una multiplicación en
cada paso por el ciclo, lo cual es análogo al caso de reducción de fuerza de hace algunas
secciones en el que se reemplazó una multiplicación con una suma. Así es como se vería
la evaluación del polinomio de fuerza reducida:

Ejemplo de Visual Basic de un método de fuerza reducida para evaluar un polinomio


valor = coeficiente ( 0 ) potencia
de X = x
Para potencia = 1 a pedido
valor = valor + coeficiente (potencia) * potenciaDeX potenciaDeX
= potenciaDeX * x
próximo

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:

Código sintonizado Tiempo

Idioma Tiempo exacto Tiempo Ahorros Relación calidad


Pitón 3.24 2.60 20% 1:1
básico visual 6.26 0.160 97% 40:1

Si te tomas en serio la reducción de fuerza, aún no te interesarán esas dos


multiplicaciones de punto flotante. El principio de reducción de fuerza sugiere que puede
reducir aún más la fuerza de las operaciones en el ciclo acumulando potencias en lugar
de multiplicarlas cada vez:

Ejemplo de Visual Basic de reducción adicional de la fuerza requerida para evaluar un


polinomio
valor = 0
Para potencia = pedido a 1 Paso -1
valor = ( valor + coeficiente( potencia ) ) * x Siguiente

valor = valor + coeficiente ( 0 )


632 Capítulo 26: Técnicas de ajuste de código

Este método elimina el extrapoder de Xvariable y reemplaza las dos


multiplicaciones en cada pasada por el ciclo con una. Los resultados:

Ahorros
Primero Segundo sobre primero

Idioma Tiempo exacto Mejoramiento Mejoramiento Mejoramiento


Pitón 3.24 2.60 2.53 3%
básico visual 6.26 0.16 0.31 - 94%

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.

Inicializar en tiempo de compilación

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:

log(x)base = log(x) / log(base)

Dada esta identidad, podría escribir una rutina como esta:

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; . . .

int sin firmar Log2 (int sin firmar x) {


return (int sin firmar) ( log( x ) / LOG2 );
}
26.4 Expresiones 633

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:

Idioma Tiempo exacto Tiempo sintonizado con código Ahorro de tiempo

C++ 9.66 5.97 38%


Java 17.0 12.3 28%
PHP 2.45 1.50 39%

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.

Tenga cuidado con las rutinas del sistema

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:

Ejemplo en C++ de una rutina Log-Base-Two basada en números enteros


int sin firmar Log2 (int sin firmar x) {
si ( x < 2 ) devuelve 0 ; si ( x < 4 )
devuelve 1 ; si ( x < 8 ) devuelve 2 ; si
( x < 16 ) devuelve 3 ; si ( x < 32 )
devuelve 4 ; si ( x < 64 ) devuelve 5 ;
si ( x < 128 ) devuelve 6 ; si ( x < 256 )
devuelve 7 ; si ( x < 512 ) devuelve 8 ;
si ( x < 1024 ) devuelve 9 ; . . .

si ( x < 2147483648 ) devuelve 30; volver 31;

}
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:

Código sintonizado Tiempo

Idioma Tiempo exacto Tiempo Ahorros Relación calidad


C++ 9.66 0.662 93% 15:1
Java 17.0 0.882 95% 20:1
PHP 2.45 3.45 - 41% 2:3

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:

Ejemplo de C++ de una rutina alternativa Log-Base-Two basada en el operador de


desplazamiento a la derecha
int sin firmar Log2 (int sin firmar x) {
CODIFICACIÓ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.

Este ejemplo destaca el valor de no detenerse después de una optimización exitosa. La


primera optimización obtuvo un ahorro respetable del 30 al 40 por ciento, pero no tuvo
el impacto de la segunda o la tercera optimización.
PUNTO CLAVE
26.4 Expresiones 635

Utilice el tipo correcto de constantes


Utilice constantes y literales con nombre que sean del mismo tipo que las variables a las que están asignados.
Cuando una constante y su variable relacionada son de tipos diferentes, el compilador tiene que hacer una
conversión de tipos para asignar la constante a la variable. Un buen compilador realiza la conversión de tipo en
tiempo de compilación para que no afecte el rendimiento en tiempo de ejecución.

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

y requieren conversiones de tipo, asumiendoXes una variable de punto flotante yies un


número entero. En el segundo caso, se ven así:

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:

Código sintonizado Tiempo

Idioma Tiempo exacto Tiempo Ahorros Relación calidad


C++ 1.11 0.000 100% no medible
C# 1.49 1.48 <1% 1:1
Java 1.66 1.11 33% 1,5:1
básico visual 0.721 0.000 100% no medible
PHP 0.872 0.847 3% 1:1

Resultados de cálculo previo

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,

consulte el Capítulo 18, "Métodos


era costoso y afectaba el rendimiento. Sin embargo, el programa reconoció relativamente pocas
controlados por tablas". distancias distintas del sol, por lo que los programadores pudieron precalcular los coeficientes de
gravedad y almacenarlos en una matriz de 10 elementos. La búsqueda de matriz fue mucho más
rápida que el costoso cálculo.

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í:

Ejemplo en Java de un cálculo complejo que podría calcularse previamente


doble CalcularPago(
largo monto del préstamo,

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:

Ejemplo de Java de cálculo previo de un cálculo complejo


doble CalcularPago(
largo monto del préstamo,

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:

Código sintonizado Tiempo

Idioma Tiempo exacto Tiempo Ahorros Relación calidad


Java 2.97 0.251 92% 10:1
Pitón 3.86 4.63 - 20% 1:1

Dependiendo de sus circunstancias, necesitará precalcular elpréstamoDivisormatriz en el momento de la


inicialización del programa o leerlo desde un archivo de disco. Alternativamente, puede inicializarlo para0,
calcule cada elemento la primera vez que se solicite, guárdelo y búsquelo cada vez que se solicite
posteriormente. Esa sería una forma de almacenamiento en caché, discutida anteriormente.

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í:

Ejemplo en Java de un segundo cálculo complejo que podría calcularse previamente


doble Calcular Pagos(
En t meses,
doble tasa de interés
){
for ( long Monto_préstamo = MIN_CANTIDAD_PRÉSTAMO; Monto_préstamo < MAX_CANTIDAD_PRÉSTAMO;
importe del préstamo++ ) {

pago = importe del préstamo / (


( 1.0 – Math.pow( 1.0+(tasa de interés/12.0), - meses ) ) / (tasa de interés/12.0 )

);
El siguiente código ...
haría algo conpago }
aquí; para el punto de este }
ejemplo, no importa qué.

Incluso sin precalcular una tabla, puede precalcular la parte complicada de la


expresión fuera del ciclo y usarla dentro del ciclo. Así es como se vería:

Ejemplo de Java de cálculo previo del segundo cálculo complejo


doble Calcular Pagos(
En t meses,
doble tasa de interés
){
préstamo a largo plazo;
Aquí está la parte que divisor doble = ( 1.0 – Math.pow( 1.0+(tasa de interés/12.0). - meses ) ) /
está precalculada. ( tasa de interés/12.0 );
for ( long Monto_préstamo = MIN_CANTIDAD_PRÉSTAMO; Monto_préstamo <= MAX_CANTIDAD_PRÉSTAMO;
importe del préstamo++ ) {

pago = monto del préstamo / divisor; . . .

}
}
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:

Código sintonizado Tiempo

Idioma Tiempo exacto Tiempo Ahorros Relación calidad


Java 7.43 0.24 97% 30:1
Pitón 5.00 1.69 66% 3:1

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.

La optimización de un programa por precálculo puede tomar varias formas:

- 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

Eliminar subexpresiones comunes


Si encuentra una expresión que se repite varias veces, asígnela a una variable y haga referencia
a la variable en lugar de volver a calcular la expresión en varios lugares. El ejemplo de cálculo
de préstamo tiene una subexpresión común que podría eliminar. Este es el código original:

Ejemplo de Java de una subexpresión común


pago = importe del préstamo / (
( 1.0 – Math.pow( 1.0 + ( tasa de interés / 12.0 ), -meses ) ) / ( tasa de interés / 12.0 )

);
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:

Ejemplo de Java de eliminación de una subexpresión común


interés mensual = tasa de interés / 12.0; pago =
importe del préstamo / (
( 1.0 – Math.pow( 1.0 + interés mensual, -meses ) ) / interés mensual

);

Los ahorros en este caso no parecen impresionantes:

Idioma Tiempo exacto Tiempo sintonizado con código Ahorro de tiempo

Java 2.94 2.83 4%


Pitón 3.91 3.94 - 1%

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.

Reescribir rutinas en línea


En los primeros días de la programación de computadoras, algunas máquinas imponían sanciones de
rendimiento prohibitivas por invocar una rutina. Una llamada a una rutina significaba que el sistema
operativo tenía que intercambiar el programa, intercambiar en un directorio de rutinas, intercambiar en la
rutina particular, ejecutar la rutina, intercambiar la rutina y volver a intercambiar la rutina de llamada. Todo
este intercambio masticó recursos e hizo que el programa fuera lento.
640 Capítulo 26: Técnicas de ajuste de código

Las computadoras modernas cobran un peaje mucho menor por llamar a una rutina. Estos son los resultados de

poner una rutina de copia de cadena en línea:

Idioma Tiempo de rutina Tiempo de código en línea Ahorro de tiempo

C++ 0.471 0.431 8%


Java 13.1 14.4 - 10%

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.

26.6 Grabación en un lenguaje de bajo nivel


Una vieja sabiduría convencional que no debe dejarse sin mencionar es el consejo de que cuando se
encuentre con un cuello de botella en el rendimiento, debe recodificar en un lenguaje de bajo nivel. Si
está programando en C++, el lenguaje de bajo nivel podría ser ensamblador. Si está codificando en
Python, el lenguaje de bajo nivel podría ser C. La recodificación en un lenguaje de bajo nivel tiende a
mejorar tanto la velocidad como el tamaño del código. Aquí hay un enfoque típico para optimizar con
un lenguaje de bajo nivel:

1.Escriba el 100 por ciento de una aplicación en un lenguaje de alto nivel.

2.Pruebe completamente la aplicación y verifique que sea correcta.

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

representa la mayor parte de su tiempo de


el 50 por ciento del tiempo de ejecución, generalmente puede identificar pequeñas partes del programa
ejecución, consulte "El principio de Pareto" como puntos calientes.
en la Sección 25.2.

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

Ejemplo Delphi de código que se adapta mejor a ensamblador


procedimiento Expansión hexadecimal(

variable fuente: matriz de bytes;


variable objetivo: matriz de palabras;

número de bytes: palabra

);
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:

Ejemplo de una Rutina Recodificada en Assembler


procedimiento Expansión hexadecimal(

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

MOVIMIENTO EDI,objetivo // objetivo compensar

XOR EAX, EAX // pone a cero el desplazamiento de la matriz

EXPANDIR:
MOVIMIENTO EBX... EAX // desplazamiento de matriz

MOVIMIENTO DL,[ESI+EBX] // obtener el byte de origen //


MOVIMIENTO DH DL copiar el byte de origen

Y DH,$F // obtener MSBS


AGREGAR DH, $ 41 // suma 65 para hacer mayúsculas

SHR DL,4 // mover lsbs a su posición //


Y DL,$F obtener lsbs
AGREGAR Licencia de conducir, $ 41 // suma 65 para hacer mayúsculas
642 Capítulo 26: Técnicas de ajuste de código

SHL caja, 1 // desplazamiento doble para la matriz de destino


MOVIMIENTO [EDI+EBX],DX desplazamiento // poner palabra de destino

CÍA EAX // incrementa el desplazamiento de la


CÍRCULO EXPANDIR matriz // repite hasta terminar
final;

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:

Idioma Tiempo de alto nivel Tiempo del ensamblador Ahorro de tiempo

C++ 4.25 3.02 29%


Delfos 5.18 3.04 41%

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.

cc2e.com/2672 LISTA DE VERIFICACIÓN: Técnicas de ajuste de código

Mejore tanto la velocidad como el tamaño

- Sustituya las búsquedas de tablas por lógica complicada.

- Atasco de bucles.

- Use números enteros en lugar de variables de punto flotante.

- Inicializar datos en tiempo de compilación.

- Utilice constantes del tipo correcto.

- Precalcular los resultados.

- Elimina las subexpresiones comunes.

- Traducir rutinas clave a un lenguaje de bajo nivel.


26.7 Cuanto más cambian las cosas, más permanecen igual 643

Mejorar solo la velocidad


- Deja de probar cuando sepas la respuesta.

- Solicitar pruebas encasodeclaraciones ysi-entonces-otroCadenas por frecuencia.

- Comparar el rendimiento de estructuras lógicas similares.

- Utilice la evaluación perezosa.

- Desactiva los bucles que contienensipruebas

- Desenrollar bucles.

- Minimice el trabajo realizado dentro de los bucles.

- Utilice centinelas en los bucles de búsqueda.

- Coloque el bucle más ocupado en el interior de los bucles anidados.

- Reducir la fuerza de las operaciones realizadas dentro de los bucles.

- Cambie las matrices multidimensionales a una sola dimensión.

- Minimice las referencias a matrices.

- Aumente los tipos de datos con índices.

- Guarda en caché los valores de uso frecuente.

- Explotar identidades algebraicas.

- Reducir la fuerza en expresiones lógicas y matemáticas.

- Tenga cuidado con las rutinas del sistema.

- Reescriba las rutinas en línea.

26.7 Cuanto más cambian las cosas, más permanecen igual


Es de esperar que los atributos de rendimiento de los sistemas hayan cambiado algo en los 10 años
transcurridos desde que escribí la primera edición deCódigo completo, y de alguna manera lo han hecho.
Las computadoras son dramáticamente más rápidas y la memoria es más abundante. En la primera edición,
realicé la mayoría de las pruebas de este capítulo entre 10 000 y 50 000 veces para obtener resultados
medibles y significativos. Para esta edición, tuve que ejecutar la mayoría de las pruebas entre 1 y 100
millones de veces. Cuando tiene que ejecutar una prueba 100 millones de veces para obtener resultados
medibles, debe preguntarse si alguien notará el impacto en un programa real. Las computadoras se han
vuelto tan poderosas que para muchos tipos comunes de programas, el nivel de optimización del
rendimiento que se analiza en este capítulo se ha vuelto irrelevante.
644 Capítulo 26: Técnicas de ajuste de código

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.

El ajuste de código implica invariablemente compensaciones entre complejidad, legibilidad,


simplicidad y mantenibilidad, por un lado, y el deseo de mejorar el rendimiento, por el otro.
Introduce un alto grado de gastos generales de mantenimiento debido a todo el reperfilado
que se requiere.

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

optimización no es lo suficientemente importante como para sacar la maquinaria de creación de perfiles, no es lo

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

impacto en la legibilidad es tan cierto como perjudicial.

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.

Apéndice 4 de BentleyPerlas de programación, 2ª ed. (Boston, MA: Addison-Wesley, 2000)


contiene un resumen de las reglas de ajuste de código de su libro anterior.
Puntos clave 645

cc2e.com/2686 También puede encontrar una gama completa de libros de optimización específicos de la tecnología. Varios se

enumeran a continuación, y el enlace web a la izquierda contiene una lista actualizada.

Booth, Rick.Inner Loops: un libro de consulta para el desarrollo rápido de software de 32 bits. Boston,
MA: Addison-Wesley, 1997.

Gerber, Ricardo.Libro de recetas de optimización de software: Recetas de alto rendimiento para la


arquitectura Intel. Prensa de Intel, 2002.

Hasan, Jeffrey y Kenneth Tu.Ajuste del rendimiento y optimización de aplicaciones


ASP.NET. Berkeley, CA: Apress, 2003.

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.

Wilson, Steve y Jeff Kesselman.Rendimiento de la plataforma Java: estrategias y tácticas.


Boston, MA: Addison-Wesley, 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.

- El ajuste de código es un poco como la energía nuclear. Es un tema controvertido y emotivo.


Algunas personas piensan que es tan perjudicial para la confiabilidad y la mantenibilidad que
no lo harán en absoluto. Otros piensan que con las debidas garantías, es beneficioso. Si
decide utilizar las técnicas de este capítulo, aplíquelas con cuidado.
Parte VI
Consideraciones del sistema

En esta parte:

Capítulo 27: Cómo afecta el tamaño del programa a la construcción . . . . . . . . . . . . . . .

.649 Capítulo 28: Gestión de la construcción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .661

Capítulo 29: Integración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .689 Capítulo 30:

Herramientas de programación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .709


capitulo 27

Cómo el tamaño del programa afecta


la construcción

cc2e.com/2761 Contenido

- 27.1 Comunicación y Tamaño: página 650

- 27.2 Gama de tamaños de proyectos: página 651

- 27.3 Efecto del tamaño del proyecto en los errores: página 651

- 27.4 Efecto del tamaño del proyecto en la productividad: página 653

- 27.5 Efecto del tamaño del proyecto en las actividades de desarrollo: página 654

Temas relacionados

- Requisitos previos a la construcción: Capítulo 3

- Determinar el tipo de software en el que está trabajando: Sección 3.2

- Gestión de la construcción: Capítulo 28

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.

Si ha estado acostumbrado a trabajar en proyectos pequeños, su primer proyecto mediano a


grande puede salirse de control y convertirse en una bestia incontrolable en lugar del éxito
placentero que había imaginado. Este capítulo le dice qué tipo de bestia esperar y dónde
encontrar el látigo y la silla para domarla. Por el contrario, si está acostumbrado a trabajar en
proyectos grandes, puede utilizar enfoques demasiado formales en un proyecto pequeño. Este
capítulo describe cómo puede economizar para evitar que un pequeño proyecto se derrumbe
bajo el peso de sus propios gastos generales.

649
650 Capítulo 27: Cómo el tamaño del programa afecta la construcción

27.1 Comunicación y tamaño


Si usted es la única persona en un proyecto, la única vía de comunicación es entre usted y el cliente, a
menos que cuente la vía a través de su cuerpo calloso, la vía que conecta el lado izquierdo de su
cerebro con el derecho. A medida que aumenta la cantidad de personas en un proyecto, también
aumenta la cantidad de vías de comunicación. El número no aumenta de manera aditiva a medida
que aumenta el número de personas. Aumenta multiplicativamente, proporcionalmente al cuadrado
del número de personas, como se ilustra en la Figura 27-1.

3 6

Ruta de comunicación Vías de comunicación Vías de comunicación


con dos programadores con tres programadores con cuatro programadores

45

10
10

Vías de comunicación Vías de comunicación


con cinco programadores con diez programadores

Figura 27-1El número de vías de comunicación aumenta proporcionalmente al cuadrado del


número de personas en el equipo.

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 enfoque típico adoptado para agilizar la comunicación es formalizarla en documentos. En lugar de


que 50 personas hablen entre sí en todas las combinaciones imaginables, 50 personas leen y
escriben documentos. Algunos son documentos de texto; algunos son gráficos. Algunos están
impresos en papel; otros se mantienen en forma electrónica.
27.2 Gama de tamaños de proyectos 651

27.2 Gama de tamaños de proyectos

¿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:

Tamaño del equipo Porcentaje aproximado de proyectos


1–3 25%
4–10 30%
11–25 20%
26–50 15%
50+ 10%
Fuente: Adaptado de “A Survey of Software Engineering Practice: Tools, Methods, and Results” (Beck y Perkins
1983),Ecosistemas ágiles de desarrollo de software(Highsmith 2002), yEquilibrio de agilidad y disciplina(Boehm y
Turner 2003).

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:

Tamaño del equipo Porcentaje aproximado de programadores


1–3 5%
4–10 10%
11–25 15%
26–50 20%
50+ 50%
Fuente: Derivado de datos en “A Survey of Software Engineering Practice: Tools, Methods, and Results” (Beck y
Perkins 1983),Ecosistemas ágiles de desarrollo de software(Highsmith 2002), yEquilibrio de agilidad y disciplina(
Boehm y Turner 2003).

27.3 Efecto del tamaño del proyecto sobre los errores

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.

Tabla 27-1 Tamaño del proyecto y densidad de errores típica

Tamaño del proyecto (en


líneas de código) Densidad de error típica

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.

27.4 Efecto del tamaño del proyecto en la productividad

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.

Tabla 27-2 Tamaño y productividad del proyecto

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)

1K 2500–25 000 (4000)


10K 2000–25 000 (3200)
100K 1000–20 000 (2600)
1,000K 700–10,000 (2,000)
10,000K 300–5000 (1600)
Fuente: Derivado de datos enMedidas para la Excelencia(Putnam y Meyers 1992),Software de fuerza industrial
(Putnam y Meyers 1997),Estimación de costos de software con Cocomo II(Boehm et al. 2000) y “Software
Development Worldwide: The State of the Practice” (Cusumano et al. 2003).

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

654 Capítulo 27: Cómo el tamaño del programa afecta la construcción

27.5 Efecto del tamaño del proyecto en las actividades de desarrollo


Si está trabajando en un proyecto de una sola persona, la mayor influencia en el éxito o el
fracaso del proyecto es usted. Si está trabajando en un proyecto de 25 personas, es concebible
que siga siendo la mayor influencia, pero es más probable que ninguna persona lleve la
medalla por esa distinción; su organización tendrá una mayor influencia en el éxito o el fracaso
del proyecto.

Actividad proporciones y tamaño


A medida que aumenta el tamaño del proyecto y aumenta la necesidad de comunicaciones formales, los tipos de

actividades que necesita un proyecto cambian drásticamente. La Figura 27-3 muestra las proporciones de

actividades de desarrollo para proyectos de diferentes tamaños.

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

actividades de construcción (diseño detallado, codificación, depuración y pruebas unitarias) aumentan

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

- Diseño y especificación de la interfaz

- Arquitectura

- Integración

- Eliminación de defectos

- Pruebas del sistema

- 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

Programas, productos, sistemas y productos del sistema


Otras lecturasPara Las líneas de código y el tamaño del equipo no son las únicas influencias en el tamaño de un
otra explicación de este
proyecto. Una influencia más sutil es la calidad y la complejidad del software final. El Gigatron
punto, véase el Capítulo 1 en
El Hombre-Mes Mítico original, el Gigatron Jr., podría haber tomado solo un mes para escribir y depurar. Era un solo
(Brooks 1995). programa escrito, probado y documentado por una sola persona. Si el Gigatron Jr. de 2500 líneas
tomó un mes, ¿por qué el Gigatron completo de 25 000 líneas tomó 20 meses?

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.

Un tipo de programa más sofisticado es un "producto" de software, un programa que está


diseñado para que lo usen personas distintas al desarrollador original. Un producto de
software se utiliza en entornos que difieren del entorno en el que se creó el producto. Se
prueba exhaustivamente antes de su lanzamiento, se documenta y es capaz de ser mantenido
por otros. Desarrollar un producto de software cuesta alrededor de tres veces más que un
programa de software.

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).

La falta de apreciación de las diferencias en el pulido y la complejidad entre programas,


productos, sistemas y productos del sistema es una causa común de errores de estimación. Los
programadores que usan su experiencia en la construcción de un programa para estimar el
cronograma para construir un producto de sistema pueden subestimar por un factor de casi
10. Al considerar el siguiente ejemplo, consulte el gráfico de la Figura 27-3 (en la página 654). Si
usó su experiencia en la escritura de 2K líneas de código para estimar el tiempo que le llevaría
desarrollar un programa de 2K, su estimación sería solo el 65 por ciento del tiempo total que
realmente necesitaría para realizar todas las actividades que implican el desarrollo. un
programa. Escribir 2000 líneas de código no lleva tanto tiempo como crear un programa
completo que contenga 2000 líneas de código. Si no tiene en cuenta el tiempo que se tarda en
realizar actividades que no son de construcción,

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

“Más” no es mejor, en lo que a metodologías se refiere. En su revisión de las metodologías


ágiles frente a las basadas en planes, Barry Boehm y Richard Turner advierten que, por lo
general, le irá mejor si comienza con métodos pequeños y los amplía para un proyecto grande
PUNTO CLAVE
que si comienza con un método integral y los reduce. para un pequeño proyecto (Boehm y
Turner 2004). Algunos expertos en software hablan de metodologías "ligeras" y "pesadas",
pero en la práctica la clave es considerar el tamaño y tipo específico de su proyecto y luego
encontrar la metodología que sea "adecuada".

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.

Cockburn, Alistair.Desarrollo Ágil de Software. Boston, MA: Addison-Wesley, 2002. El Capítulo 4


analiza los problemas relacionados con la selección de metodologías de proyecto apropiadas,
incluido el tamaño del proyecto. El Capítulo 6 presenta las Metodologías Crystal de Cockburn, que son
enfoques definidos para desarrollar proyectos de varios tamaños y grados de criticidad.

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

DeGrace, Peter y Leslie Stahl.Problemas perversos, soluciones correctas: un catálogo de paradigmas


modernos de ingeniería de software. Englewood Cliffs, NJ: Yourdon Press, 1990. Como sugiere el
título, este libro cataloga enfoques para el desarrollo de software. Como se indica a lo largo de este
capítulo, su enfoque debe variar a medida que varía el tamaño del proyecto, y DeGrace y Stahl lo
expresan claramente. La sección titulada "Atenuación y truncamiento" en el Capítulo 5 analiza la
personalización de los procesos de desarrollo de software en función del tamaño y la formalidad del
proyecto. El libro incluye descripciones de modelos de la NASA y el Departamento de Defensa y un
notable número de ilustraciones edificantes.

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, la productividad será menor en un proyecto grande que en uno


pequeño.

- 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

- 28.1 Fomentar la buena codificación: página 662

- 28.2 Gestión de la configuración: página 664

- 28.3 Estimación de un cronograma de construcción: página 671

- 28.4 Medición: página 677


- 28.5 Tratar a los programadores como personas: página 680

- 28.6 Gestión de su gerente: página 686

Temas relacionados

- Requisitos previos a la construcción: Capítulo 3

- Determinar el tipo de software en el que está trabajando: Sección 3.2

- Tamaño del programa: Capítulo 27

- Calidad del software: Capítulo 20

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 está interesado en la administración de software, asegúrese de leer la Sección 3.2, “Determine el


tipo de software en el que está trabajando”, para comprender la diferencia entre los enfoques
secuenciales tradicionales para el desarrollo y los enfoques iterativos modernos. Asegúrese también
de leer el Capítulo 20, “El panorama de la calidad del software”, y el Capítulo 27, “Cómo afecta el
tamaño del programa a la construcción”. Los objetivos de calidad y el tamaño del proyecto afectan
significativamente cómo se debe administrar un proyecto de software específico.

28.1 Fomento de la buena codificación


Debido a que el código es el resultado principal de la construcción, una pregunta clave en la gestión
de la construcción es "¿Cómo fomenta las buenas prácticas de codificación?" En general, exigir un
conjunto estricto de normas técnicas desde el puesto de dirección no es una buena idea. Los
programadores tienden a ver a los gerentes en un nivel más bajo de evolución técnica, en algún lugar
entre los organismos unicelulares y los mamuts lanudos que se extinguieron durante la Edad de
Hielo, y si va a haber estándares de programación, los programadores deben aceptarlos.

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.

Consideraciones en el establecimiento de estándares

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.

Técnicas para fomentar la buena codificación


Esta sección describe varias técnicas para lograr buenas prácticas de codificación que son
menos estrictas que establecer estándares de codificación rígidos:

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

formales”, y la Sección 21.4, “Otros


nombre para la revisión por pares es "presión de pares". Además de proporcionar una red de
tipos de prácticas de desarrollo seguridad en caso de que el programador original abandone el proyecto, las revisiones mejoran la
colaborativo”.
calidad del código porque el programador sabe que el código será leído por otros. Incluso si su
tienda no ha creado estándares de codificación explícitos, las revisiones brindan una forma sutil de
avanzar hacia un estándar de codificación grupal: el grupo toma decisiones durante las revisiones y,
con el tiempo, el grupo deriva sus propios estándares.

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

información, consulte la Sección 33.5 y la


código es parte del proyecto y debe estar disponible gratuitamente para cualquier otra persona del proyecto
Sección 34.3. que lo necesite. Debe ser visto por otros durante las revisiones y el mantenimiento, aunque no sea en
ningún otro momento.

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:

- La recompensa debe ser algo que el programador quiera. (Muchos programadores


encuentran desagradables las recompensas de "attaboy", especialmente cuando provienen de
gerentes no técnicos).

- 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.

Un estándar fácilSi está administrando un proyecto de programación y tiene experiencia en


programación, una técnica fácil y efectiva para obtener un buen trabajo es decir "Debo poder leer y
comprender cualquier código escrito para el proyecto". El hecho de que el administrador no sea el
experto técnico más destacado puede ser una ventaja, ya que podría desalentar el código
"inteligente" o engañoso.

El papel de este libro


La mayor parte de este libro es una discusión de buenas prácticas de programación. No tiene la
intención de usarse para justificar estándares rígidos, y menos aún para usarse como un conjunto de
estándares rígidos. Utilice este libro como base para la discusión, como fuente de buenas prácticas de
programación y para identificar prácticas que podrían ser beneficiosas en su entorno.

28.2 Gestión de la configuración


Un proyecto de software es dinámico. El código cambia, el diseño cambia y los requisitos
cambian. Además, los cambios en los requisitos generan más cambios en el diseño, y los
cambios en el diseño generan aún más cambios en el código y los casos de prueba.

¿Qué es la gestión de la configuración?


La gestión de la configuración es la práctica de identificar los artefactos del proyecto y manejar los cambios
de manera sistemática para que un sistema pueda mantener su integridad a lo largo del tiempo. Otro
nombre para esto es "control de cambios". Incluye técnicas para evaluar los cambios propuestos, realizar un
seguimiento de los cambios y mantener copias del sistema tal como existía en varios momentos.

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.

Los problemas continúan indefinidamente. Si los cambios no se manejan sistemáticamente, está


dando pasos aleatorios en la niebla en lugar de moverse directamente hacia un destino claro. Sin un
buen control de cambios, en lugar de desarrollar código, está perdiendo el tiempo luchando. La
gestión de la configuración le ayuda a utilizar su tiempo de forma eficaz.

3 A pesar de la obvia necesidad de la gestión de la configuración, muchos programadores la han estado


2
1
evitando durante décadas. Una encuesta realizada hace más de 20 años encontró que más de un tercio de

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,

“Cómo el tamaño del programa afecta la


siendo útil (y, de hecho, utilicé la gestión de la configuración para crear este manuscrito). En un proyecto
construcción”. grande de 50 personas, probablemente necesitará un esquema SCM completo, que incluya procedimientos
bastante formales para copias de seguridad, control de cambios para requisitos y diseño, y control sobre
documentos, código fuente, contenido, casos de prueba y otros. artefactos del proyecto. Si su proyecto no es
ni muy grande ni muy pequeño, tendrá que conformarse con un grado de formalidad en algún lugar entre
los dos extremos. Las siguientes subsecciones describen algunas de las opciones para implementar SCM.
666 Capítulo 28: Gestión de la construcción

Requisitos y cambios de diseño


Referencia cruzadaAlguno Durante el desarrollo, seguramente estará lleno de ideas sobre cómo mejorar el sistema. Si
enfoques de desarrollo
implementa cada cambio a medida que se le ocurre, pronto se encontrará caminando sobre
apoyar los cambios mejor que otros. Para

obtener más información, consulte la


una rueda de ardilla de software; a pesar de todo lo que el sistema cambiará, no se acercará a
Sección 3.2, “Determine el tipo de software su finalización. Aquí hay algunas pautas para controlar los cambios de diseño:
en el que está trabajando”.

Seguir un procedimiento sistemático de control de cambiosComo se señaló en la Sección 3.4, un


procedimiento sistemático de control de cambios es una bendición cuando tiene muchas solicitudes de
cambio. Al establecer un procedimiento sistemático, deja en claro que los cambios se considerarán en el
contexto de lo que es mejor para el proyecto en general.

Manejar solicitudes de cambio en gruposEs tentador implementar cambios fáciles a medida


que surgen ideas. El problema de manejar los cambios de esta manera es que los cambios
buenos pueden perderse. Si piensa en un cambio simple el 25 por ciento del camino a través
del proyecto y está dentro del cronograma, hará el cambio. Si piensa en otro cambio simple al
50 por ciento del camino a través del proyecto y ya está atrasado, no lo hará. Cuando comience
a quedarse sin tiempo al final del proyecto, no importará que el segundo cambio sea 10 veces
mejor que el primero, no estará en condiciones de realizar cambios no esenciales. Algunos de
los mejores cambios pueden pasar desapercibidos simplemente porque pensó en ellos más
tarde que temprano.

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

Cambios en los requisitos durante la


los diseños de alto nivel no se realizaron lo suficientemente bien como para respaldar una construcción
construcción” en la Sección 3.4. Para efectiva. La copia de seguridad para trabajar en los requisitos o la arquitectura puede parecer costosa, pero
obtener consejos sobre cómo manejar los
no será tan costosa como construir el software más de una vez o desechar el código de funciones que
cambios de código de manera segura
realmente no necesitaba.
cuando ocurren, consulte el Capítulo 24,

"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.

Cambios de código de software

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.

Por esta modesta inversión de esfuerzo, obtiene varios grandes beneficios:

- 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.

cc2e.com/2843 LISTA DE VERIFICACIÓN: Gestión de la configuración


General
- ¿Su plan de gestión de configuración de software está diseñado para ayudar a los
programadores y minimizar los gastos generales?

- ¿Su enfoque de SCM evita el control excesivo del proyecto?

- ¿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)?

- ¿Estima sistemáticamente el costo, el cronograma y el impacto en la calidad de


cada cambio propuesto?

- ¿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 facilitar la gestión de la configuración?

- ¿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

gráficos y las notas importantes?

- ¿Has probado el procedimiento de copia de seguridad-recuperación?

Recursos adicionales sobre la gestión de la configuración


cc2e.com/2850 Dado que este libro trata sobre la construcción, esta sección se ha centrado en el control de cambios desde
el punto de vista de la construcción. Pero los cambios afectan a los proyectos en todos los niveles, y una
estrategia integral de control de cambios debe hacer lo mismo.

Hass, Anne Mette Jonassen.Principios y prácticas de gestión de la configuración. Boston, MA:


Addison-Wesley, 2003. Este libro proporciona una visión general de la gestión de configuración
de software y detalles prácticos sobre cómo incorporarla en su proceso de desarrollo de
software. Se centra en la gestión y el control de elementos de configuración.

Berczuk, Stephen P. y Brad Appleton.Patrones de gestión de configuración de software: trabajo en


equipo efectivo, integración práctica. Boston, MA: Addison-Wesley, 2003. Al igual que el libro de Hass,
este libro proporciona una descripción general de SCM y es práctico. Complementa el libro de Hass
proporcionando pautas prácticas que permiten a los equipos de desarrolladores aislar y coordinar su
trabajo.

cc2e.com/2857 SPMN.Pequeño libro de gestión de la configuración. Arlington, VA: Software Program


Managers Network, 1998. Este folleto es una introducción a las actividades de gestión de
configuración y define los factores críticos de éxito. Está disponible como descarga gratuita
desde el sitio web de SPMN enwww.spmn.com/products_guidebooks.html.

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

28.3 Estimación de un cronograma de construcción

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).

- Haga que expertos en estimación externos calculen el proyecto.

- Tenga una reunión de recorrido para las estimaciones.

- Calcule partes del proyecto y luego sume las partes.


- Haga que las personas calculen sus propias tareas y luego sume las estimaciones de tareas.

- Consulte la experiencia en proyectos anteriores.

- Mantenga las estimaciones anteriores y vea qué tan precisas fueron. Úselos para ajustar nuevas
estimaciones.

En "Recursos adicionales sobre la estimación de software" al final de esta sección se brindan


indicaciones para obtener más información sobre estos enfoques. Aquí hay un buen enfoque para
estimar un proyecto:

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

sea valiosa. La estimación es un


producirán los mismos resultados, así que pruebe varios de ellos. Estudiar los diferentes
caso en el que la iteración es útil. resultados de los diferentes enfoques. Los niños aprenden temprano que si le piden a cada
Para obtener un resumen de las
padre individualmente un tercer tazón de helado, tienen más posibilidades de obtener al
técnicas iterativas, consulte la

Sección 34.8, “Iterar,


menos un "sí" que si le piden solo a uno de los padres. A veces los padres se dan cuenta y dan
repetidamente, una y otra vez”. la misma respuesta; a veces no lo hacen. Vea qué diferentes respuestas puede obtener de
diferentes técnicas de estimación.

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.

Reestimar periódicamenteLos factores en un proyecto de software cambian después de la


estimación inicial, así que planifique actualizar sus estimaciones periódicamente. Como ilustra la
figura 28-2, la precisión de sus estimaciones debería mejorar a medida que avanza hacia la
finalización del proyecto. De vez en cuando, compare sus resultados reales con sus resultados
estimados y use esa evaluación para refinar las estimaciones para el resto del proyecto.
28.3 Estimación de un cronograma de construcción 673

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.

Estimación de la cantidad de construcción


Referencia cruzadaPara obtener detalles La medida en que la construcción tendrá una influencia importante en el cronograma de un proyecto
sobre la cantidad de codificación para
depende en parte de la proporción del proyecto que se dedicará a la construcción, entendida como
proyectos de varios tamaños, consulte

"Proporciones y tamaño de actividad" en


diseño detallado, codificación y depuración, y pruebas unitarias. Eche otro vistazo a la Figura 27-3 en
la Sección 27.5. la página 654. Como muestra la figura, la proporción varía según el tamaño del proyecto. Hasta que
su empresa tenga sus propios datos de historial de proyectos, la proporción de tiempo dedicado a
cada actividad que se muestra en la figura es un buen lugar para comenzar a estimar sus proyectos.

La mejor respuesta a la pregunta de cuánta construcción requerirá un proyecto es que la


proporción variará de un proyecto a otro y de una organización a otra. Mantenga registros de
la experiencia de su organización en los proyectos y utilícelos para estimar el tiempo que
llevarán los proyectos futuros.
674 Capítulo 28: Gestión de la construcción

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

intuitiva. Consulte el Capítulo 27, “Cómo


estudios de programas comerciales han cuantificado algunos de los factores y se muestran en la tabla 28-1.
afecta el tamaño del programa a la

construcción”, para obtener una

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á)

Cohesión del equipo - 10% 11%


Experiencia del equipo en el área de aplicaciones - 19% 22%
Experiencia del equipo en la plataforma tecnológica - 15% 19%
Restricción de tiempo (de la propia aplicación) 0% 63%
Uso de herramientas de software. - 22% 17%
Fuente:Estimación de costos de software con Cocomo II(Boehm et al. 2000).

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).

- Experiencia y capacidad del desarrollador de requisitos

- Experiencia y capacidad del programador.


28.3 Estimación de un cronograma de construcción 675

- Motivación del equipo

- Calidad de gestión
- Cantidad de código reutilizado

- Rotación de personal

- Volatilidad de los requisitos

- Calidad de la relación con el cliente.

- Participación del usuario en los requisitos.

- Experiencia del cliente con el tipo de aplicación

- Grado en que los programadores participan en el desarrollo de requisitos

- Entorno de seguridad clasificado para computadoras, programas y datos.

- 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.

Qué hacer si estás atrasado


El proyecto promedio excede su cronograma planificado en aproximadamente un 100 por ciento, como se
mencionó anteriormente en este capítulo. Cuando estás atrasado, aumentar la cantidad de tiempo no suele
ser una opción. Si es así, hazlo. De lo contrario, puede probar una o más de estas soluciones:

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,

consulte el Capítulo 14, "Control


la depuración, las pruebas y la documentación de esa función. Eliminas la interfaz de esa
de conjuntos de funciones", en función con otras funciones.
Desarrollo rápido(McConnell
1996). Cuando planee el producto inicialmente, divida las capacidades del producto en
"imprescindibles", "agradables de tener" y "opcionales". Si se atrasa, priorice los "opcionales"
y los "agradables de tener" y descarte los que son menos importantes.

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

Recursos adicionales sobre estimación de software


cc2e.com/2871 Aquí hay algunas referencias adicionales sobre la estimación de software:

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.

Conte, SD, HE Dunsmore y VY Shen.Métricas y modelos de ingeniería de software. Menlo Park,


CA: Benjamin/Cummings, 1986. El Capítulo 6 contiene un buen estudio de las técnicas de
estimación, que incluye una historia de la estimación, modelos estadísticos, modelos basados
en la teoría y modelos compuestos. El libro también demuestra el uso de cada técnica de
estimación en una base de datos de proyectos y compara las estimaciones con la duración real
de los proyectos.

Gil, Tom.Principios de Gestión de Ingeniería de Software. Wokingham, Inglaterra: Addison-Wesley,


1988. El título del Capítulo 16, "Diez principios para estimar los atributos del software", es algo
irónico. Gilb argumenta en contra de la estimación del proyecto ya favor del control del proyecto. Al
señalar que las personas realmente no quieren predecir con precisión, pero sí quieren controlar los
resultados finales, Gilb establece 10 principios que puede usar para dirigir un proyecto para cumplir
con una fecha límite del calendario, una meta de costos u otro objetivo del proyecto.

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

tendría sin ella (Gilb 2004) .

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.

Argumentar en contra de la medición es argumentar que es mejor no saber lo que


realmente está sucediendo en su proyecto.Cuando mides un aspecto de un proyecto, sabes
algo sobre él que no sabías antes. Puede ver si el aspecto se hace más grande o más pequeño
o permanece igual. La medida le da una ventana a por lo menos ese aspecto de su proyecto.
La ventana puede ser pequeña y nublada hasta que refine sus medidas, pero será mejor que
ninguna ventana. Argumentar en contra de todas las mediciones porque algunas no son
concluyentes es argumentar en contra de las ventanas porque algunas están turbias.

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.

Tabla 28-2 Medidas útiles de desarrollo de software


Tamaño Calidad general
Líneas totales de código escritas Número total de defectos
Líneas totales de comentarios Número de defectos en cada clase o rutina
Número total de clases o rutinas Promedio de defectos por mil líneas de código
Declaraciones de datos totales Tiempo medio entre fallas
Líneas en blanco totales Errores detectados por el compilador

Seguimiento de defectos mantenibilidad


Gravedad de cada defecto Número de rutinas públicas en cada clase Número de
Ubicación de cada defecto (clase o rutina) parámetros pasados a cada rutina Número de rutinas
Origen de cada defecto (requisitos, diseño, privadas y/o variables en cada
construcción, prueba) clase
Forma en que se corrige cada Número de variables locales utilizadas por cada rutina
defecto Responsable de cada defecto Número de rutinas llamadas por cada clase o rutina
Número de líneas afectadas por cada corrección de defectos Número de puntos de decisión en cada rutina Complejidad
Horas de trabajo dedicadas a corregir cada defecto del flujo de control en cada rutina
Tiempo medio necesario para encontrar un defecto Tiempo Líneas de código en cada clase o rutina Líneas de
medio necesario para corregir un defecto Número de intentos comentarios en cada clase o rutina Número de
realizados para corregir cada defecto Número de nuevos declaraciones de datos en cada clase o rutina Número de
errores resultantes del defecto líneas en blanco en cada clase o rutina Número deirs en
corrección cada clase o rutina Número de declaraciones de entrada o
salida en cada clase
o rutina
Productividad

Horas de trabajo dedicadas al proyecto Horas de trabajo


dedicadas a cada clase o rutina Número de veces que
cambió cada clase o rutina Dólares gastados en el
proyecto
Dólares gastados por línea de código
Dólares gastados por defecto
28.4 Medición 679

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.

No comience recopilando datos sobre todas las medidas posibles; se enterrará en


datos tan complejos que no podrá descifrar lo que significan. Comience con un
conjunto simple de medidas, como el número de defectos, el número de meses de
trabajo, el total de dólares y el total de líneas de código. Estandarice las medidas en
todos sus proyectos y luego refínelas y agréguelas a medida que mejore su
comprensión de lo que desea medir (Pietrasanta 1990).

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).

Recursos adicionales sobre medición de software


cc2e.com/2878 Aquí hay recursos adicionales:

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.

Jones, Alcaparras.Medición de Software Aplicado: Asegurando Productividad y Calidad,2d ed. Nueva


York, NY: McGraw-Hill, 1997. Jones es un líder en medición de software y su libro es una acumulación
de conocimiento en esta área. Proporciona la teoría y la práctica definitivas de las técnicas de
medición actuales y describe los problemas con las mediciones tradicionales. Presenta un programa
completo para recopilar "métricas de puntos de función". Jones ha recopilado y analizado una gran
cantidad de datos de calidad y productividad, y este libro resume los resultados en un solo lugar,
incluido un capítulo fascinante sobre los promedios para el desarrollo de software en EE. UU.
680 Capítulo 28: Gestión de la construcción

Grady, Roberto B.Métricas prácticas de software para la gestión de proyectos y la mejora de


procesos. Englewood Cliffs, NJ: Prentice Hall PTR, 1992. Grady describe las lecciones
aprendidas al establecer un programa de medición de software en Hewlett-Packard y le indica
cómo establecer un programa de medición de software en su organización.

Conte, SD, HE Dunsmore y VY Shen.Métricas y modelos de ingeniería de software. Menlo Park,


CA: Benjamin/Cummings, 1986. Este libro cataloga el conocimiento actual de la medición de
software alrededor de 1986, incluidas las mediciones de uso común, las técnicas
experimentales y los criterios para evaluar los resultados experimentales.

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/2892 Laboratorio de Ingeniería de Software de la NASA.Guía de medición de software, junio de 1995,


NASA-GB-001-94. Esta guía de alrededor de 100 páginas es probablemente la mejor fuente de
información práctica sobre cómo configurar y ejecutar un programa de medición. Se puede
descargar desde el sitio web de la NASA.

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.

28.5 Tratar a los programadores como personas


La abstracción de la actividad de programación exige una naturalidad compensatoria en el entorno
de la oficina y contactos ricos entre compañeros de trabajo. Las empresas altamente técnicas ofrecen
campus corporativos similares a parques, estructuras organizativas orgánicas, oficinas cómodas y
PUNTO CLAVE
otras características ambientales de "alto contacto" para equilibrar la intelectualidad intensa, a veces
árida, del trabajo en sí. Las empresas técnicas más exitosas combinan elementos de alta tecnología y
alto contacto (Naisbitt 1982). Esta sección describe las formas en que los programadores son más que
reflejos orgánicos de sus alter egos de silicio.
28.5 Tratar a los programadores como personas 681

¿Cómo pasan su tiempo los programadores?


Los programadores pasan su tiempo programando, pero también pasan su tiempo en reuniones, en
capacitaciones, en leer su correo y simplemente en pensar. Un estudio de 1964 en Bell Laboratories
encontró que los programadores pasaban su tiempo de esta manera, como se describe en la tabla 28-3.

Tabla 28-3 Una vista de cómo los programadores emplean su tiempo

Operando
Fuente Correo/Misc. Técnico Procedimientos, Programa
Actividad Código Negocio Personal Reuniones Capacitación Documentos Manuales Varios Prueba Totales

hablar o escuchar 4% 17% 7% 3% 1% 32%


Hablar con 1% 1%
gerente
Teléfono 2% 1% 3%
Leer 14% 2% 2% 18%
escribir/grabar 13% 1% 14%

fuera o fuera 4% 1% 4% 6% 15%


Caminando 2% 2% 1% 1% 6%
Misceláneas 2% 3% 3% 1% 1% 1% 11%

Totales 35% 29% 13% 7% 6% 5% 2% 2% 1% 100%


Fuente: "Estudios de investigación de programadores y programación" (Bairdain 1964, informado en Boehm 1981).

Estos datos se basan en un estudio de tiempo y movimiento de 70 programadores. Los datos


son antiguos y las proporciones de tiempo dedicado a las diferentes actividades variarían
entre los programadores, pero los resultados invitan a la reflexión. Alrededor del 30 por ciento
del tiempo de un programador se dedica a actividades no técnicas que no ayudan
directamente al proyecto: caminar, asuntos personales, etc. Los programadores de este
estudio pasaron el seis por ciento de su tiempo caminando; eso es unas 2,5 horas a la semana,
unas 125 horas al año. Puede que no parezca mucho hasta que te das cuenta de que los
programadores dedican tanto tiempo cada año a caminar como a capacitarse, tres veces más
tiempo del que dedican a leer manuales técnicos y seis veces más al que dedican a hablar con
sus gerentes. Personalmente, no he visto muchos cambios en este patrón hoy.

Variación en el rendimiento y la calidad

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

Específicamente en programación, muchos estudios han mostrado diferencias de orden de


magnitud en la calidad de los programas escritos, el tamaño de los programas escritos y la
productividad de los programadores.

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.

3 Aunque proporciones específicas como 25 a 1 no son particularmente significativas,


2
1
afirmaciones más generales como "Hay diferencias de orden de magnitud entre los

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).

Variación del equipo

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.

Un estudio anterior de equipos de programación observó una diferencia de 5 a 1 en el tamaño del


programa y una variación de 2,6 a 1 en el tiempo requerido para que un equipo complete el mismo
proyecto (Weinberg y Schulman 1974).

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

- Compensaciones entre eficiencia y legibilidad

- Elección de metodología, por ejemplo, Scrum vs. Programación extrema vs.


entrega evolutiva

- Utilidades de programación

- Convenciones de nombres

- Uso deirs
- Uso de variables globales

- Mediciones, especialmente medidas de productividad como líneas de código por día.

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:

Tenga en cuenta que está tratando con un área sensibleSondee al programador en


cada tema emocional antes de saltar con ambos pies.

Usar “sugerencias” o “guías” con respecto al áreaEvite establecer “reglas” o


“estándares” rígidos.

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

684 Capítulo 28: Gestión de la construcción

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.

A continuación, vaya a la ciudad, busque una tienda de programación, busque un gerente de


programación y pregunte cuánto dinero en equipo tiene el gerente de programación para cada
trabajador. El gerente de programación mirará una oficina y verá un escritorio, una silla, algunos
libros y una computadora y le dirá que cuesta menos de $25,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:

Factor medioambiental 25% superior 25% inferior


Espacio de piso dedicado 78 pies cuadrados 46 pies cuadrados

Espacio de trabajo aceptablemente silencioso 57% sí 29% sí


Espacio de trabajo aceptablemente privado 62% sí 19% sí
Capacidad para silenciar el teléfono 52% sí 10% si
Habilidad para desviar llamadas Frecuentes 76% sí 19% sí
interrupciones innecesarias 38% sí 76% sí
Espacio de trabajo que hace programador
sentirse apreciado 57% sí 29% sí
Fuente:gente(DeMarco y Lister 1999).
28.5 Tratar a los programadores como personas 685

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.

Recursos adicionales sobre programadores como seres humanos


cc2e.com/2806 Aquí hay recursos adicionales:

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

McConnell, Steve.Desarrollo de software profesional. Boston, MA: Addison-Wesley, 2004. El Capítulo 7,


"Preferidos por los huérfanos", resume los estudios sobre la demografía de los programadores, incluidos
los tipos de personalidad, los antecedentes educativos y las perspectivas laborales.

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.

28.6 Gestión de su gerente


En el desarrollo de software, los gerentes no técnicos son comunes, al igual que los gerentes que
tienen experiencia técnica pero que están 10 años atrasados. Los gerentes técnicamente
competentes y técnicamente actualizados son raros. Si trabaja para uno, haga lo que pueda para
mantener su trabajo. Es un regalo inusual.

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).

- Niéguese a hacer lo que su jefe le diga e insista en hacer su trabajo de la manera


correcta.

- Encuentra otro 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

Recursos adicionales sobre la gestión de la construcción


cc2e.com/2813 Aquí hay algunos libros que cubren temas de interés general en la gestión de proyectos de software:

Gil, Tom.Principios de Gestión de Ingeniería de Software. Wokingham, Inglaterra: Addison-Wesley,


1988. Gilb ha trazado su propio rumbo durante treinta años, y la mayor parte del tiempo ha estado
por delante de la manada, ya sea que la manada se dé cuenta o no. Este libro es un buen ejemplo.
Este fue uno de los primeros libros en discutir las prácticas de desarrollo evolutivo, la gestión de
riesgos y el uso de inspecciones formales. Gilb es muy consciente de los enfoques de vanguardia; de
hecho, este libro publicado hace más de 15 años contiene la mayoría de las buenas prácticas que
actualmente vuelan bajo el lema "Agile". Gilb es increíblemente pragmático, y el libro sigue siendo
uno de los mejores libros de administración de software.

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 1058-1998, Estándar para Planes de Gestión de Proyectos de Software.

IEEE Std 12207-1997, Tecnología de la información—Procesos del ciclo de vida del software.

IEEE Std 1045-1992, estándar para métricas de productividad de software.

IEEE Std 1062-1998, Práctica recomendada para la adquisición de software.

IEEE Std 1540-2001, Estándar para procesos del ciclo de vida del software—Gestión de riesgos.

IEEE Std 828-1998, Estándar para Planes de Gestión de Configuración de Software

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.

- La gestión de la configuración, cuando se aplica correctamente, facilita el trabajo de los


programadores. Esto incluye especialmente el control de cambios.

- 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

- 29.1 Importancia del Enfoque de Integración: página 689

- 29.2 Frecuencia de integración: ¿por fases o incremental?: página 691

- 29.3 Estrategias de integración incremental: página 694

- 29.4 Prueba diaria de construcción y humo: página 702

Temas relacionados

- Pruebas de desarrollador: Capítulo 22

- Depuración: Capítulo 23

- Gestión de la construcción: Capítulo 28

El término "integración" se refiere a la actividad de desarrollo de software en la que se combinan


componentes de software separados en un solo sistema. En proyectos pequeños, la integración
podría consistir en pasar una mañana conectando un puñado de clases. En proyectos grandes, puede
consistir en semanas o meses de vincular conjuntos de programas. Independientemente del tamaño
de la tarea, se aplican principios comunes.

El tema de la integración está entrelazado con el tema de la secuencia de construcción. El


orden en que crea clases o componentes afecta el orden en que puede integrarlos; no
puede integrar algo que aún no se ha creado. Tanto la integración como la secuencia de
construcción son temas importantes. Este capítulo aborda ambos temas desde el punto
de vista de la integración.

29.1 Importancia del enfoque de integración


En campos de la ingeniería distintos al software, es bien conocida la importancia de una integración
adecuada. El Noroeste del Pacífico, donde vivo, vio una ilustración dramática de los peligros de una
integración deficiente cuando el estadio de fútbol de la Universidad de Washington se derrumbó a
mitad de la construcción, como se muestra en la Figura 29-1.

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.

No importa que el estadio hubiera sido lo suficientemente fuerte para cuando se


terminó; necesitaba ser lo suficientemente fuerte en cada paso. Si construye e integra el
software en el orden incorrecto, será más difícil codificar, probar y depurar. Si nada de
eso funciona hasta que todo funcione, puede parecer que nunca se terminará. También
puede colapsar por su propio peso durante la construcción: la cantidad de errores puede
parecer insuperable, el progreso puede ser invisible o la complejidad puede ser
abrumadora, aunque el producto terminado hubiera funcionado.

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.

Puede esperar algunos de estos beneficios de una integración cuidadosa:

- Diagnóstico de defectos más fácil

PUNTO CLAVE
- Menos defectos

- Menos andamios

- Tiempo más corto para el primer producto de trabajo

- Cronogramas de desarrollo general más cortos

- Mejores relaciones con los clientes

- moral mejorada
- Mayor probabilidad de finalización del proyecto

- Estimaciones de horarios más confiables

- Informes de estado más precisos

- Calidad de código mejorada

- 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.

29.2 Frecuencia de integración: ¿por fases o incremental?


Los programas se integran por medio de un enfoque gradual o incremental.

Integración por fases


Hasta hace unos años, la integración por etapas era la norma. Sigue estos pasos o fases
bien definidos:

1.Diseñe, codifique, pruebe y depure cada clase. Este paso se llama “desarrollo de la unidad”.

2.Combine las clases en un gran sistema ("integración del sistema").


3.Probar y depurar todo el sistema. Esto se llama "desintegración del sistema".
(Gracias a Meilir Page-Jones por esta ingeniosa observación).

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.

La incertidumbre sobre la ubicación de cualquiera de los problemas específicos se ve agravada por el


hecho de que todos los problemas se presentan repentinamente a la vez. Esto lo obliga a lidiar no
solo con problemas causados por interacciones entre clases, sino también con problemas que son
difíciles de diagnosticar porque los problemas mismos interactúan. Por esta razón, otro nombre para
la integración por fases es "integración big bang", como se muestra en la figura 29-2.

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

"Software Oyster Farming: System


siga estos pasos:
Accretion" y "Software

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.

2.Diseñe, codifique, pruebe y depure una clase.

3.Integrar la nueva clase con el esqueleto. Pruebe y depure la combinación de esqueleto y


nueva clase. Asegúrese de que la combinación funcione antes de agregar nuevas
clases. Si queda trabajo por hacer, repita el proceso desde el paso 2.

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

Beneficios de la Integración Incremental


El enfoque incremental ofrece muchas ventajas sobre el enfoque por fases tradicional,
independientemente de la estrategia incremental que utilice:

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.

Tst ikocos ptosm s ptosm


ptosm. m ikocos
m ikocos Ylkr kis U jy eteb
Ylkr kis ptosm. ksi. Si
U jy eteb m ik ocos Ubrid
m
oco ptosm. Ylkr kis susnd

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

por fases incrementales


Integración Integración

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.

Puede construir el sistema con un cronograma de desarrollo más cortoSi la integración se


planifica cuidadosamente, puede diseñar parte del sistema mientras se codifica otra parte. Esto no
reduce la cantidad total de horas de trabajo necesarias para desarrollar el diseño y el código
completos, pero permite que se realice algo de trabajo en paralelo, una ventaja cuando el tiempo de
calendario es escaso.

La integración incremental apoya y fomenta otras estrategias incrementales. Las


ventajas del incrementalismo aplicado a la integración son la punta del iceberg.

29.3 Estrategias de Integración Incremental


Con la integración por fases, no tiene que planificar el orden en que se construyen los componentes
del proyecto. Todos los componentes se integran al mismo tiempo, por lo que puede crearlos en
cualquier orden siempre que estén listos para el día D.

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.

Las estrategias de orden de integración vienen en una variedad de formas y tamaños, y


ninguna es la mejor en todos los casos. El mejor enfoque de integración varía de un proyecto a
otro, y la mejor solución es siempre la que crea para satisfacer las demandas específicas de un
proyecto específico. Conocer los puntos en la recta numérica metodológica le dará una idea de
las posibles soluciones.

Integración de arriba hacia abajo

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

de nombre. Para obtener más información sobre el

diseño de arriba hacia abajo, consulte "Desde arriba


y luego integra todas las clases en el siguiente nivel (Nivel 2). Cuando haya integrado todas las clases
hacia abajo". del Nivel 2, y no antes, integre las clases del Nivel 3. La rigidez en la integración de arriba hacia abajo
y diseño de abajo hacia arriba
pura es completamente arbitraria. Es difícil imaginar que alguien se tome la molestia de usar la
Enfoques” en la Sección 5.4.
integración pura de arriba hacia abajo. La mayoría de las personas utilizan un enfoque híbrido, como
la integración de arriba hacia abajo en secciones.

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

Finalizar Finalizar Finalizar

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

Comienzo Comienzo Comienzo

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.

Integración Orientada al Riesgo

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.

Mayor riesgo: Riesgo mínimo:

hacer primero. hacer el último

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

Integración orientada a funciones

Otro enfoque es integrar una característica a la vez. El término "característica" no se refiere a


nada sofisticado, solo a una función identificable del sistema que está integrando. Si está
escribiendo un procesador de textos, una característica podría estar subrayando en la pantalla
o reformateando el documento automáticamente, algo así.

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

(menús, tal vez)

Característica 2 Característica 3 Característica 4 Característica 5 Característica 6

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

para los sistemas orientados a objetos.

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

Resumen de enfoques de integración


De abajo hacia arriba, de arriba hacia abajo, sándwich, orientado al riesgo, orientado a las características, en
forma de T: ¿tiene la sensación de que las personas están inventando estos nombres a medida que
avanzan? Están. Ninguno de estos enfoques son procedimientos sólidos que debe seguir metódicamente
desde el paso 1 hasta el paso 47 y luego declarar que ha terminado. Al igual que los enfoques de diseño de
software, son heurísticas más que algoritmos y, en lugar de seguir cualquier procedimiento de manera
dogmática, usted sale adelante al crear una estrategia única adaptada a su proyecto específico.

29.4 Prueba diaria de construcción y humo


Otras lecturasGran parte de Independientemente de la estrategia de integración que seleccione, un buen enfoque para integrar el
esta discusión está adaptada
software es la "prueba diaria de compilación y humo". Cada archivo se compila, vincula y combina en un
del Capítulo 18 deDesarrollo
rápido(McConnell 1996). Si ha programa ejecutable todos los días, y luego el programa se somete a una "prueba de humo", una
leído esa discusión, puede verificación relativamente simple para ver si el producto "humea" cuando se ejecuta.
pasar directamente a la sección
"Integración continua". Este proceso simple produce varios beneficios significativos. Reduce el riesgo de baja calidad, que es
un riesgo relacionado con el riesgo de una integración fallida o problemática. Al hacer pruebas de
humo de todo el código diariamente, se evita que los problemas de calidad tomen el control del
proyecto. Usted lleva el sistema a un buen estado conocido y luego lo mantiene allí. Simplemente no
permita que se deteriore hasta el punto en que puedan ocurrir problemas de calidad que consuman
mucho tiempo.

Este proceso también facilita el diagnóstico de defectos. Cuando el producto se construye y


prueba todos los días, es fácil determinar por qué el producto se rompe en un día
determinado. Si el producto funcionó el día 17 y se rompe el día 18, algo que sucedió entre las
dos compilaciones rompió el producto.

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.

Estos son algunos de los entresijos del uso de compilaciones diarias:

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.

Como mínimo, una "buena" construcción debe

- Compile todos los archivos, bibliotecas y otros componentes correctamente.

- Vincule todos los archivos, bibliotecas y otros componentes con éxito.

- 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

Automatice la prueba diaria de compilación y humoEl cuidado y la alimentación de la construcción


pueden llevar mucho tiempo. Automatizar la compilación y la prueba de humo ayuda a garantizar que se
genere el código y se ejecute la prueba de humo. No es práctico construir y probar el humo diariamente sin
automatizació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

completo en el grupo de construcción (Zachary 1994).

Agregue revisiones a la compilación solo cuando tenga sentido hacerlo...Los desarrolladores


individuales generalmente no escriben código lo suficientemente rápido como para agregar incrementos
significativos al sistema diariamente. Deben trabajar en un fragmento de código y luego integrarlo cuando
tengan una colección de código en un estado consistente, generalmente una vez cada pocos días.

. . . pero no espere demasiado para agregar un conjunto de revisionesTenga cuidado de verificar


el código con poca frecuencia. Es posible que un desarrollador se involucre tanto en un conjunto de
revisiones que todos los archivos del sistema parezcan estar involucrados. Eso socava el valor de la
construcción diaria. El resto del equipo seguirá aprovechando los beneficios de la integración
incremental, pero ese desarrollador en particular no lo hará. Si un desarrollador pasa más de un par
de días sin verificar un conjunto de cambios, considere que el trabajo de ese desarrollador está en
riesgo. Como señala Kent Beck, la integración frecuente a veces te obliga a dividir la construcción de
una función única en varios episodios. Esa sobrecarga es un precio aceptable a pagar por el riesgo de
integración reducido, la visibilidad mejorada del estado, la capacidad de prueba mejorada y otros
beneficios de la integración frecuente (Beck 2000).

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.

¿Qué tipos de proyectos pueden usar el proceso de compilación diaria?

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

cc2e.com/2992 LISTA DE VERIFICACIÓN: Integración

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?

- ¿La estrategia conduce a un diagnóstico fácil de los defectos?

- ¿La estrategia mantiene el andamiaje al mínimo?

- ¿Es la estrategia mejor que otros enfoques?

- ¿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).

Prueba diaria de construcción y humo

- ¿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?

- ¿Has automatizado la construcción y la prueba de humo?

- ¿Los desarrolladores verifican su código con frecuencia, no pasando más de un día o


dos entre los registros?

- ¿La prueba de humo se mantiene actualizada con el código, ampliándose a medida que el código se

expande?

- ¿Es una construcción rota una ocurrencia rara?

- ¿Construye y prueba el software incluso cuando está bajo presión?

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.

Boehm, Barry W. "Un modelo en espiral de desarrollo y mejora de software".Computadora,


mayo de 1988: 61–72. En este artículo, Boehm describe su “modelo en espiral” de desarrollo de
software. Presenta el modelo como un enfoque para gestionar el riesgo en un proyecto de
desarrollo de software, por lo que el artículo trata sobre el desarrollo en general y no sobre la
integración en particular. Boehm es uno de los principales expertos del mundo en los aspectos
generales del desarrollo de software, y la claridad de sus explicaciones refleja la calidad de su
comprensión.

Gil, Tom.Principios de Gestión de Ingeniería de Software. Wokingham, Inglaterra: Addison-


Wesley, 1988. Los capítulos 7 y 15 contienen discusiones exhaustivas sobre la entrega
evolutiva, uno de los primeros enfoques de desarrollo incremental.

Beck, Kent.Explicación de la programación extrema: aceptar el cambio. Reading, MA: Addison-Wesley,


2000. Este libro contiene una presentación más moderna, más concisa y más evangélica de muchas
de las ideas del libro de Gilb. Personalmente prefiero la profundidad del análisis presentado en el
libro de Gilb, pero algunos lectores pueden encontrar la presentación de Beck más accesible o más
directamente aplicable al tipo de proyecto en el que están trabajando.

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.

- Un orden de integración bien pensado reduce el esfuerzo de prueba y facilita la depuración.

- 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

proporcionar información útil para la gestión de proyectos.


capitulo 30

Herramientas de programación

cc2e.com/3084 Contenido

- 30.1 Herramientas de diseño: página 710

- 30.2 Herramientas de código fuente: página 710

- 30.3 Herramientas de código ejecutable: página 716

- 30.4 Entornos orientados a herramientas: página 720

- 30.5 Creación de sus propias herramientas de programación: página 721

- 30.6 Herramienta Fantasyland: página 722

Temas relacionados

- Herramientas de control de versiones: en la Sección 28.2

- Herramientas de depuración: Sección 23.5

- Herramientas de soporte de pruebas: Sección 22.5

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

útiles, te estás perdiendo algo que podrías usar mucho.

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.

30.1 Herramientas de diseño

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.

30.2 Herramientas de código fuente

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.

Entornos de desarrollo integrado (IDE)


3 Algunos programadores estiman que pasan hasta el 40 por ciento de su tiempo editando el
2
1
código fuente (Parikh 1986, Ratliff 1987). Si ese es el caso, gastar unos cuantos dólares extra en

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:

- Compilación y detección de errores desde el editor


- Integración con herramientas de control, compilación, prueba y depuración de código fuente
30.2 Herramientas de código fuente 711

- Vistas comprimidas o esquematizadas de programas (solo nombres de clase o estructuras


lógicas sin el contenido, también conocido como "plegado")

- Saltar a definiciones de clases, rutinas y variables

- Saltar a todos los lugares donde se usa una clase, rutina o variable

- Formato específico del idioma

- Ayuda interactiva para el idioma que se está editando

- llave (principio-fin) coincidencia

- Plantillas para construcciones de lenguaje común (el editor completa la estructura de unpor
bucle después de que el programador escribapor, por ejemplo)

- Sangría inteligente (que incluye cambiar fácilmente la sangría de un bloque de declaraciones


cuando cambia la lógica)

- Transformaciones o refactorizaciones de código automatizadas

- Macros programables en un lenguaje de programación familiar

- Listado de cadenas de búsqueda para que no sea necesario volver a escribir las cadenas de uso común

- Expresiones regulares en buscar y reemplazar

- Buscar y reemplazar en un grupo de archivos

- Edición de varios archivos simultáneamente

- Comparaciones de diferencias lado a lado

- Deshacer multinivel

Teniendo en cuenta algunos de los editores primitivos que todavía están en uso, es posible que se sorprenda al

saber que varios editores incluyen todas estas capacidades.

Búsqueda y reemplazo de cadenas de archivos múltiples

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í:

grep "\[ *[0–9]+ *\]" *.cpp


712 Capítulo 30: Herramientas de programación

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.

Embellecedores de código fuente

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

Herramientas de documentación de la interfaz

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.

Herramientas de referencia cruzada

Una herramienta de referencias cruzadas enumera variables y rutinas y todos los lugares en los que se
utilizan, normalmente en páginas web.

Generadores de jerarquía de clases

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.

Análisis de la calidad del código

Las herramientas de esta categoría examinan el código fuente estático para evaluar su calidad.

Comprobadores exigentes de sintaxis y semántica

Los verificadores de sintaxis y semántica complementan su compilador al verificar el código más a


fondo de lo que normalmente lo hace el compilador. Su compilador puede verificar solo errores de
sintaxis rudimentarios. Un verificador de sintaxis exigente podría usar matices del lenguaje
Traducido del inglés al español - www.onlinedoctranslator.com

714 Capítulo 30: Herramientas de programación

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

mientras que ( i = 0 ) ...

es una declaración perfectamente legal, pero por lo general está destinado a ser

mientras que ( yo == 0 ) ...

La primera línea es sintácticamente correcta, pero cambiando=y==es un error común y la línea


probablemente sea incorrecta. Lint es un comprobador de sintaxis y semántica exigente que puede
encontrar en muchos entornos C/C++. Lint te advierte sobre variables no inicializadas, variables
completamente sin usar, variables a las que se asignan valores y nunca se usan, parámetros de una
rutina que se pasan fuera de la rutina sin que se les asigne un valor, operaciones de puntero
sospechosas, comparaciones lógicas sospechosas (como la de la ejemplo que se acaba de mostrar),
código inaccesible y muchos otros problemas comunes. Otros idiomas ofrecen herramientas
similares.

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).

Código fuente de refactorización

Algunas herramientas ayudan a convertir el código fuente de un formato a otro.

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

las herramientas de refactorización, consulte el Capítulo 14, "Herramientas de refactorización" enrefactorización(

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

descrito en “Cambios en el código de


- Control de código fuente
software” en la Sección 28.2.

- Control de dependencia como el que ofrece la utilidad make asociada con UNIX

- Versionado de la documentación del proyecto

- 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

30.3 Herramientas de código ejecutable

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

Las herramientas descritas en esta sección ayudan con la 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

para ser compilados, aunque algunos todavía se interpretan.

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

herramienta ant que se usa para los programas Java.

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.

Algunos grupos han encontrado alternativas interesantes a las herramientas de comprobación de


dependencias como make. Por ejemplo, el grupo de Microsoft Word descubrió que simplemente reconstruir
todos los archivos de origen era más rápido que realizar una verificación de dependencia extensa con make,
siempre que los archivos de origen estuvieran optimizados (contenido del archivo de encabezado, etc.). Con
este enfoque, la máquina del desarrollador promedio en el proyecto de Word podría reconstruir todo el
ejecutable de Word (varios millones de líneas de código) en aproximadamente 13 minutos.

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

- Servicios de transacciones con tarjeta de crédito (servicios de comercio electrónico)

- Herramientas de desarrollo multiplataforma. Puede escribir código que se ejecute en


Microsoft Windows, Apple Macintosh y X Window System simplemente recompilando
para cada entorno.

- Herramientas de compresión de datos

- Tipos de datos y algoritmos

- Operaciones de base de datos y herramientas de manipulación de archivos de datos

- Herramientas de diagramación, gráficos y diagramas

- herramientas de imagen

- Administradores de licencias

- Operaciones matemáticas

- Herramientas de redes y comunicaciones por Internet.

- Generadores de informes y creadores de consultas de informes

- Herramientas de seguridad y encriptación

- Herramientas de hoja de cálculo y cuadrícula

- Herramientas de texto y ortografía

- Herramientas de voz, teléfono y fax


718 Capítulo 30: Herramientas de programación

Asistentes de generación de código

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

Numerosos proveedores proporcionan herramientas que admiten la creación de programas de instalación.


Estas herramientas suelen admitir la creación de discos, CD o DVD o la instalación a través de la Web.
Comprueban si los archivos de biblioteca comunes ya existen en la máquina de instalación de destino,
realizan la comprobación de versiones, etc.

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,

consulte "Plan para eliminar las ayudas


desarrollo, si desea verificar la fragmentación de la memoria al comienzo de cada rutina,
para la depuración" en la Sección 8.6. puede usar una macro al comienzo de cada rutina. Es posible que no desee dejar las
comprobaciones en el código de producción, por lo que para el código de producción puede
redefinir la macro para que no genere ningún código. Por razones similares, las macros de
preprocesador son buenas para escribir código destinado a ser compilado en múltiples
entornos, por ejemplo, tanto en Windows como en Linux.
30.3 Herramientas de código ejecutable 719

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

descrito en la Sección 23.5, “Herramientas de


- Mensajes de advertencia del compilador
depuración—Obviamente

ous y no tan obvio ". - Andamio de prueba

- Herramientas Diff (para comparar diferentes versiones de archivos de código fuente)

- Perfiladores de ejecución

- Seguimiento de monitores

- Depuradores interactivos, tanto de software como de hardware

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

descrito en la Sección 22.5, “Herramientas


- Marcos de prueba automatizados como JUnit, NUnit, CppUnit, etc.
de soporte de pruebas”.

- Generadores de prueba automatizados

- Utilidades de grabación y reproducción de casos de prueba

- Monitores de cobertura (analizadores lógicos y perfiladores de ejecución)

- Depuradores simbólicos

- Perturbadores del sistema (rellenos de memoria, sacudidores de memoria, fallos selectivos de memoria,

comprobadores de acceso a la memoria)

- Herramientas de comparación (para comparar archivos de datos, resultados capturados e imágenes de pantalla)

- Andamio
- Herramientas de inyección de defectos

- Software de seguimiento de defectos


720 Capítulo 30: Herramientas de programación

Ajuste de código

Estas herramientas pueden ayudarlo a ajustar su 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.

Listados de ensambladores y desensambladores

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.

Si no se siente cómodo con el lenguaje ensamblador y desea una introducción, no encontrará


una mejor que comparar cada declaración de lenguaje de alto nivel que escriba con las
instrucciones del ensamblador generadas por el compilador. Una primera exposición al
ensamblador es a menudo una pérdida de inocencia. Cuando vea cuánto código crea el
compilador, cuánto más de lo que necesita, nunca volverá a ver su compilador de la misma
manera.

Por el contrario, en algunos entornos el compilador debe generar código extremadamente


complejo. Estudiar la salida del compilador puede fomentar una apreciación de cuánto trabajo se
requeriría para programar en un lenguaje de nivel inferior.

30.4 Entornos orientados a herramientas


Algunos entornos han demostrado ser más adecuados para la programación orientada a herramientas
que otros.

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).

de eric raymondEl arte de la programación Unix(2004) contiene un análisis profundo de la


cultura de programación de UNIX.

30.5 Creación de sus propias herramientas de programación

Suponga que le dan cinco horas para hacer el trabajo y tiene una opción:

- Haga el trabajo cómodamente en cinco horas, o

- Dedique cuatro horas y 45 minutos a construir febrilmente una herramienta para hacer el trabajo y luego

haga que la herramienta haga el trabajo en 15 minutos.

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.

Herramientas específicas del proyecto

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

herramientas específicas del proyecto:

- Un equipo aeroespacial se encargó de desarrollar un software en vuelo para controlar un sensor de


infrarrojos y analizar sus datos. Para verificar el rendimiento del software, un registrador de datos en
vuelo documentó las acciones del software en vuelo. Los ingenieros escribieron herramientas
personalizadas de análisis de datos para analizar el rendimiento de los sistemas en vuelo. Después
de cada vuelo, usaron las herramientas personalizadas para verificar los sistemas primarios.

- 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í:

crypto c:\palabra\diario.* %1 /d /Es /s palabra c:


\palabra\diario.doc
crypto c:\palabra\diario.* %1 /Es /s

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.

30.6 Herramienta Fantasyland


Referencia cruzadaLa disponibilidad Durante décadas, los proveedores de herramientas y los expertos de la industria han prometido que las
de herramientas depende en parte de
herramientas necesarias para eliminar la programación están en el horizonte. La primera herramienta, y
la madurez del entorno técnico. Para

obtener más información sobre esto,


quizás la más irónica, en recibir este apodo fue Fortran. Fortran o "Lenguaje de traducción de fórmulas" fue
consulte la Sección 4.3, “Su ubicación concebido para que los científicos e ingenieros pudieran simplemente escribir fórmulas, eliminando así
en la ola tecnológica”.
supuestamente la necesidad de programadores.

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

computadoras", ¡corra! O al menos sonríe ante el ingenuo optimismo del vendedor.


724 Capítulo 30: Herramientas de programación

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.

Jones, Alcaparras.Estimación de costos de software. Nueva York, NY: McGraw-Hill, 1998.

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.

cc2e.com/3019 Lista de verificación: herramientas de programación

- ¿Tienes un IDE efectivo?


- ¿Su IDE admite la integración con el control del código fuente? herramientas de
compilación, prueba y depuración; y otras funciones útiles?

- ¿Tiene herramientas que automaticen refactorizaciones comunes?

- ¿Utiliza el control de versiones para administrar el código fuente, el contenido, los requisitos, los

diseños, los planes del proyecto y otros artefactos del proyecto?

- Si está trabajando en un proyecto muy grande, ¿está utilizando un diccionario de datos


o algún otro repositorio central que contenga descripciones autorizadas de cada clase
utilizada en el sistema?

- ¿Ha considerado las bibliotecas de código como alternativas a la escritura de código personalizado, cuando

estén disponibles?
Puntos clave 725

- ¿Está utilizando un depurador interactivo?


- ¿Utiliza make u otro software de control de dependencias para crear programas de
manera eficiente y confiable?

- ¿Su entorno de prueba incluye un marco de prueba automatizado, generadores de prueba


automatizados, monitores de cobertura, perturbadores del sistema, herramientas de diferencias y
software de seguimiento de defectos?

- ¿Ha creado herramientas personalizadas que ayudarían a satisfacer las necesidades de su proyecto

específico, especialmente herramientas que automatizan tareas repetitivas?

- En general, ¿su entorno se beneficia de un soporte de herramientas adecuado?

Puntos clave
- Los programadores a veces pasan por alto algunas de las herramientas más poderosas durante
años antes de descubrirlas.

- Las buenas herramientas pueden hacer su vida mucho más fácil.

- 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.

- Puede hacer muchas de las herramientas especiales que necesita.

- 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:

Capítulo 31: Diseño y estilo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .729


Capítulo 32: Código de autodocumentación . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.777 Capítulo 33: Carácter personal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .819
Capítulo 34: Temas en la artesanía del software . . . . . . . . . . . . . . . . . . . .837
Capítulo 35: Dónde encontrar más información . . . . . . . . . . . . . . . . . . . . . .855
capitulo 31

Diseño y estilo
cc2e.com/3187 Contenido

- 31.1 Fundamentos del diseño: página 730

- 31.2 Técnicas de diseño: página 736

- 31.3 Estilos de diseño: página 738

- 31.4 Diseño de estructuras de control: página 745

- 31.5 Diseño de declaraciones individuales: página 753

- 31.6 Diseño de comentarios: página 763

- 31.7 Diseño de rutinas: página 766

- 31.8 Disposición de clases: página 768

Temas relacionados

- Código autodocumentado: Capítulo 32

- Herramientas de formato de código: "Edición" en la Sección 30.2

Este capítulo se centra en un aspecto estético de la programación de computadoras: el diseño del


código fuente del programa. El disfrute visual e intelectual del código bien formateado es un placer
que pocos no programadores pueden apreciar. Pero los programadores que se enorgullecen de su
trabajo obtienen una gran satisfacción artística al pulir la estructura visual de su código.

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

31.1 Fundamentos del diseño


Esta sección explica la teoría de un buen diseño. El resto del capítulo explica la
práctica.

Extremos de diseño

Considere la rutina que se muestra en el Listado 31-1:

Listado 31-1Ejemplo de diseño de Java #1.

/* 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.

La rutina es sintácticamente correcta. Está completamente comentado y tiene buenos nombres


de variables y una lógica clara. ¡Si no lo cree, léalo y encuentre un error! Lo que la rutina no
tiene es un buen diseño. Este es un ejemplo extremo, que se dirige hacia el "infinito negativo"
en la recta numérica del diseño de malo a bueno. El listado 31-2 es un ejemplo menos extremo:

Listado 31-2Ejemplo de diseño de Java #2.

/* 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

int 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 pasada por el bucle, sortBoundary
se incrementa, 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. */

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; }

/* Reemplazar el elemento del límite inferior original */


data[ firstElement-1 ] = lowerBoundary;
}

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.

Listado 31-3Ejemplo de diseño de Java #3.

/* 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 ) {


// Reemplace el elemento en el límite inferior con un elemento garantizado para ser // el
primero en una lista ordenada.
int 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 pasada por el bucle, sortBoundary
se incrementa, 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.

*/
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

datos[ insertarPos ] = insertarVal;


}

// Reemplazar elemento de límite inferior original


data[ firstElement - 1 ] = lowerBoundary;
}

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!

El teorema fundamental del formato


El Teorema fundamental del formato dice que un buen diseño visual muestra la
estructura lógica de un programa.

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.

Interpretaciones Humanas y Computacionales de un Programa


Cualquier tonto puede escribir un código El diseño es una pista útil para la estructura de un programa. Mientras que la computadora podría
que una computadora pueda entender.
preocuparse exclusivamente por frenos oempezaryfinal, un lector humano puede extraer pistas de
Los buenos programadores escriben

código que los humanos


la presentación visual del código. Considere el fragmento de código en el Listado 31-4, en el que el
puedan entender. esquema de sangría hace que a un humano le parezca que se ejecutan tres instrucciones cada vez
—Martín Cazador que se ejecuta el ciclo.

Listado 31-4Ejemplo de Java de diseño que cuenta diferentes historias para humanos y computadoras.

// intercambiar los elementos izquierdo y derecho por toda la


matriz para ( i = 0; i < MAX_ELEMENTS; i++ )
elementoizquierdo = izquierda[ i ];
izquierda [yo] = derecho[ yo ];
cierto [ yo ] = elementoizquierdo;
31.1 Fundamentos del diseño 733

Si el código no tiene llaves, el compilador ejecutará la primera declaración


MAX_ELEMENTOSveces y la segunda y tercera declaraciones una vez cada uno. La
sangría deja claro para usted y para mí que el autor del código quería que las tres
declaraciones se ejecutaran juntas y tenía la intención de colocar llaves alrededor de
ellas. Eso no quedará claro para el compilador. El listado 31-5 es otro ejemplo:

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.

¿Cuánto vale un buen diseño?


Nuestros estudios respaldan la afirmación de que el conocimiento de los
planes de programación y las reglas del discurso de programación pueden
tener un impacto significativo en la comprensión del programa. En su libro
llamado [The] Elements of [Programming] Style, Kernighan y Plauger también
identifican lo que llamaríamos reglas de discurso. Nuestros resultados
empíricos confirman estas reglas: no es simplemente una cuestión de
estética que los programas deban escribirse en un estilo particular. Más bien,
existe una base psicológica para escribir programas de manera convencional:
los programadores tienen fuertes expectativas de que otros programadores
sigan estas reglas de discurso. Si se violan las reglas, entonces se anula
efectivamente la utilidad proporcionada por las expectativas que los
programadores han construido a lo largo del tiempo.

— Elliot Soloway y Kate Ehrlich

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,

consulte la Sección 34.3, “Escriba


más pequeña del trabajo de programación es escribir un programa para que la computadora pueda
programas para las personas primero, las leerlo; la mayor parte es escribirlo para que otros humanos puedan leerlo.
computadoras después”.

En su artículo clásico "Percepción en el ajedrez", Chase y Simon informaron sobre un estudio


que comparó las habilidades de expertos y novatos para recordar las posiciones de las piezas
de ajedrez (1973). Cuando las piezas estaban dispuestas en el tablero como podrían estar
durante un juego, la memoria de los expertos era muy superior a la de los novatos. Cuando las
piezas se dispusieron al azar, hubo poca diferencia entre los recuerdos de los expertos y los
novatos. La interpretación tradicional de este resultado es que un
734 Capítulo 31: Diseño y estilo

la memoria de un experto no es intrínsecamente mejor que la de un novato, pero el experto tiene


una estructura de conocimiento que le ayuda a recordar determinados tipos de información. Cuando
la nueva información corresponde a la estructura del conocimiento, en este caso, la ubicación
sensata de las piezas de ajedrez, el experto puede recordarla fácilmente. Cuando la nueva
información no se corresponde con una estructura de conocimiento (las piezas de ajedrez están
colocadas al azar), el experto no puede recordarla mejor que el novato.

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

Diseño como religión


La importancia para la comprensión y la memoria de estructurar el entorno propio de una manera familiar
ha llevado a algunos investigadores a plantear la hipótesis de que el diseño podría dañar la capacidad de un
experto para leer un programa si el diseño es diferente del esquema que utiliza el experto (Sheil 1981,
Soloway y Ehrlich 1984). ). Esa posibilidad, agravada por el hecho de que el diseño es un ejercicio tanto
estético como lógico, significa que los debates sobre el formato de los programas a menudo suenan más a
guerras religiosas que a discusiones filosóficas.

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.

Objetivos de un buen diseño


Los resultados señalan la Muchas decisiones sobre los detalles del diseño son una cuestión de estética subjetiva; a menudo, puede
fragilidad de la experiencia en
lograr el mismo objetivo de muchas maneras. Puede hacer que los debates sobre temas subjetivos sean
programación: los programadores

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.

Además de estos criterios, a veces también se considera minimizar la cantidad de líneas de


código necesarias para implementar una declaración o bloque simple.

Cómo poner en uso los objetivos del diseño

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.

31.2 Técnicas de diseño


Puede lograr un buen diseño utilizando algunas herramientas de diseño de varias maneras diferentes. Esta
sección describe cada uno de ellos.

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

libro y la estructura de un programa.


imposible hojearlo en busca de una línea de pensamiento o para encontrar un pasaje
Para obtener información, consulte importante. Quizás más importante, el diseño del libro no mostraría al lector cómo el
"El paradigma del libro para la
autor pretendía organizar la información. La organización del autor es una pista
documentación del programa" en la

Sección 32.5.
importante para la organización lógica del tema.

Dividir un libro en capítulos, párrafos y oraciones le muestra al lector cómo organizar


mentalmente un tema. Si la organización no es evidente, el lector tiene que proporcionar la
organización, lo que supone una carga mucho mayor para el lector y añade la posibilidad de
que el lector nunca descubra cómo está organizado el tema.

La información contenida en un programa es más densa que la información contenida en la


mayoría de los libros. Mientras que puede leer y comprender una página de un libro en un
minuto o dos, la mayoría de los programadores no pueden leer y comprender una lista de
programas desnudos a esa velocidad. Un programa debería dar más pistas organizativas que
un libro, no menos.
31.2 Técnicas de diseño 737

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í.

Líneas en blancoAsí como es importante agrupar declaraciones relacionadas, es importante


separar declaraciones no relacionadas entre sí. El inicio de un nuevo párrafo en inglés se
identifica con sangría o una línea en blanco. El comienzo de un nuevo párrafo de código debe
identificarse con una línea en blanco.

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

SangríaUse sangría para mostrar la estructura lógica de un programa. Como regla


general, debe sangrar las sentencias bajo la sentencia a la que están lógicamente
subordinadas.

3 Se ha demostrado que la sangría se correlaciona con una mayor comprensión del


2
1
programador. El artículo “Sangría y comprensibilidad del programa” informó que varios

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

Versión de Microsoft Visual Basic:12 + 4 mod 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.

31.3 Estilos de diseño


La mayoría de los problemas de diseño tienen que ver con la disposición de bloques, los grupos de
instrucciones debajo de las instrucciones de control. Un bloque está encerrado entre llaves o
palabras clave:{y}en C++ y Java,si-entonces-endifen Visual Basic, y otras estructuras similares en otros
lenguajes. Para simplificar, gran parte de esta discusión utilizaempezaryfinalde forma genérica,
suponiendo que pueda averiguar cómo se aplica la discusión a las llaves en C++ y Java u otros
mecanismos de bloqueo en otros lenguajes. Las siguientes secciones describen cuatro estilos
generales de diseño:

- Bloques puros

- Emulando bloques puros

- Usandoprincipio-finpares (llaves) para designar límites de bloque

- Diseño de línea final

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

Listado 31-6Ejemplo de Visual Basic de un purosibloquear.

Si pixelColor = Color_Red Entonces


declaración1
declaración2
...
Terminara si

Listado 31-7 Ejemplo de Visual Basic de un purotiempobloquear.

Mientras que pixelColor = Color_Red


declaración1
declaración2
...
Encaminarse a

Listado 31-8 Ejemplo de Visual Basic de un purocasobloquear.

Seleccione Color del píxel del caso

Caso Color rojo


declaración1
declaración2
...
Color de la caja_Verde
declaración1
declaración2
...
caso más
declaración1
declaración2
...
Finalizar Seleccionar

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:

Listado 31-9Ejemplo abstracto del estilo de diseño de bloques puros.

A XXXXXXXXXXXXXXXXXXX
B XXXXXXXXXXX
C XXXXXXXXXXXXXX
D XXXX
740 Capítulo 31: Diseño y estilo

En este ejemplo, la declaración A comienza la construcción de control y la declaración D finaliza la


construcción de control. La alineación entre los dos proporciona un cierre visual sólido.

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.

Emulación de bloques puros

Un buen enfoque en lenguajes que no tienen bloques puros es ver elempezaryfinal


palabras clave (o{y}tokens) como extensiones de la construcción de control con la que se
usan. Entonces es sensato tratar de emular el formato de Visual Basic en su idioma. El
Listado 31-10 es una vista abstracta de la estructura visual que intentas emular:

Listado 31-10Ejemplo abstracto del estilo de diseño de bloques puros.

A XXXXXXXXXXXXXXXXXXX
B XXXXXXXXXXXX
C XXXXXXXXXXXXXX
D XXXX

En este estilo, la estructura de control abre el bloque en la declaración A y termina el bloque en


la declaración D. Esto implica que elempezardebe estar al final de la declaración A y elfinal
debería ser la declaración D. En abstracto, para emular bloques puros, tendría que hacer algo
como el Listado 31-11:

Listado 31-11Ejemplo abstracto de emulación del estilo pure-block.

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:

Listado 31-12Ejemplo en C++ de emulación de un purosibloquear.

if (color de píxel == Color_Rojo) {


declaración1;
declaración2;
...
}

Listado 31-13 Ejemplo en C++ de emulación de un purotiempobloquear.

while (color de píxel == Color_Rojo) {


declaración1;
declaración2;
...
}

Listado 31-14 Ejemplo en C++ de emulación de un purointerruptor/casobloquear.

cambiar (color de píxel) {


caso Color_Rojo:
declaración1;
declaración2;
...
descanso;

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

Usandoprincipio-finPares (llaves) para designar límites de bloque


Un sustituto de una estructura de bloques puros es verprincipio-finpares como límites de bloque. (La
siguiente discusión utilizaprincipio-finreferirse genéricamente aprincipio-finpares, llaves y otras
estructuras lingüísticas equivalentes.) Si toma ese enfoque, verá el empezary elfinalcomo
declaraciones que siguen a la construcción de control en lugar de como fragmentos que forman
parte de ella. Gráficamente, este es el ideal, tal como sucedió con la emulación de bloques puros que
se muestra nuevamente en el Listado 31-15:

Listado 31-15Ejemplo abstracto del estilo de diseño de bloques puros.

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:

Listado 31-16Ejemplo abstracto de usoempezaryfinalcomo límites de bloque.

A XXXXXXXXXXXXXXXXXXX
{XXXXXXXXXXXXXXXX
B XXXXXXXXXXXXXXXXX
C XXXXXXXXXXXXXXXX
}X

Algunos ejemplos de cómo usarempezaryfinalEl aspecto de los límites de bloque en C++ se


muestra en el Listado 31-17, el Listado 31-18 y el Listado 31-19:

Listado 31-17C++ ejemplo de usoempezaryfinalcomo límites de bloque en unsibloquear.

if (color de píxel == Color_Rojo)


{
declaración1;
declaración2;
...
}

Listado 31-18C++ ejemplo de usoempezaryfinalcomo límites de bloque en untiempobloquear.

while (color de píxel == Color_Rojo)


{
declaración1;
declaración2;
...
}
31.3 Estilos de diseño 743

Listado 31-19C++ ejemplo de usoempezaryfinalcomo límites de bloque en uninterruptor/caso


bloquear.

cambiar (color de píxel)


{
caso Color_Rojo:
declaración1;
declaración2;
...
descanso;

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).

Diseño de línea final

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:

Listado 31-20Ejemplo abstracto del estilo de diseño de línea final.

A XXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXX X
B XXXXXXXXXXXXX
C XXXXXXXXXXXXXX
D XX

En este ejemplo, la instrucción A inicia la construcción de control y la instrucción D la finaliza. Las


declaraciones B, C y D están alineadas bajo la palabra clave que inició el bloque en la declaración A.
Traducido del inglés al español - www.onlinedoctranslator.com

744 Capítulo 31: Diseño y estilo

La sangría uniforme de B, C y D muestra que están agrupados. El Listado 31-21 es un


ejemplo menos abstracto de código formateado usando esta estrategia:

Listado 31-21Ejemplo de Visual Basic del diseño final de untiempobloquear.

Mientras (color de píxel = Color_Rojo)


declaración1;
declaración2;
...
Encaminarse a

En el ejemplo, elempezarse coloca al final de la línea en lugar de debajo de la palabra clave


correspondiente. Algunas personas prefieren ponerempezarbajo la palabra clave, pero elegir
entre esos dos puntos finos es el menor de los problemas de este estilo.

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.

Si (contador vendido > 1000) Entonces


reducción = 0,10
ganancia = 0.05
losmásla palabra clave está alineada con Más
ladespuéspalabra clave encima de ella. reducción = 0,05
Final Si

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.

Si miras críticamente lo anteriorcaso-ejemplo de declaración, probablemente puedas predecir el


desmoronamiento de este estilo. A medida que la expresión condicional se vuelve más complicada, el
estilo dará pistas inútiles o engañosas sobre la estructura lógica. El listado 31-23 es un ejemplo de
cómo el estilo se rompe cuando se usa con un condicional más complicado:

Listado 31-23Un ejemplo más típico de Visual Basic, en el que el diseño de la línea final se descompone.

Si (cuentaVendidas > 10 y VentasMesAnteriores > 10) Entonces


Si (cuentaVendidas > 100 y VentasPrevMonth > 10) Entonces
Si (contador vendido > 1000) Entonces
CODIFICACIÓN reducción = 0,1
HORROR
ganancia = 0.05
Más
reducción = 0,05
Terminara si

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.

¿Qué estilo es mejor?


Si está trabajando en Visual Basic, use sangría de bloque puro. (El IDE de Visual Basic hace que sea
difícil no usar este estilo de todos modos).

En Java, la práctica estándar es usar sangría de bloque puro.

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.

31.4 Diseño de estructuras de control


Referencia cruzadaPara obtener detalles El diseño de algunos elementos del programa es principalmente una cuestión de estética. Sin embargo, el
sobre la documentación del control
diseño de las estructuras de control afecta la legibilidad y la comprensión y, por lo tanto, es una prioridad
estructuras, consulte "Estructuras
de control de comentarios" en la
práctica.
Sección 32.5. Para una discusión
de otros aspectos de las
estructuras de control, vea los
Capítulos 14 a 19.
746 Capítulo 31: Diseño y estilo

Puntos finos de formateo de bloques de estructura de control

Trabajar con bloques de estructura de control requiere atención a algunos detalles finos. Aquí hay
algunas pautas:

Evitar sin sangríaprincipio-finparejasEn el estilo que se muestra en el Listado 31-24, elprincipio-fin


par está alineado con la estructura de control, y las declaraciones queempezaryfinal adjunto están
sangrados debajoempezar.

Listado 31-24Ejemplo de Java de sin sangríaprincipio-finpares

losempezarestá alineado para ( int i = 0; i < MAX_LINES; i++ ) {


con elpor.
Línea de lectura (yo );
Las declaraciones están ProcesarLínea( i );
sangradas bajoempezar. }

losfinalestá alineado con


elpor. Aunque este enfoque se ve bien, viola el Teorema fundamental del formato; no muestra la
estructura lógica del código. Utilizado de esta manera, elempezaryfinalno son parte de la
construcción de control, pero tampoco son parte de las declaraciones posteriores.

El Listado 31-25 es una vista abstracta de este enfoque:

Listado 31-25Ejemplo abstracto de sangría engañosa.

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.

Evite la doble sangría conempezaryfinalUn corolario de la regla contra nonindented


principio-finpares es la regla contra doble sangríaprincipio-finpares En este estilo, mostrado en
el Listado 31-26,empezaryfinalestán sangradas y las declaraciones que encierran están
sangradas nuevamente:

Listado 31-26Ejemplo de Java de sangría doble inapropiada deprincipio-finbloquear.

para (int i = 0; i < MAX_LINES; i++)


CODIFICACIÓN

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.

El enfoque también exagera la complejidad de la estructura lógica de un programa. ¿Cuál de las


estructuras que se muestran en el Listado 31-27 y el Listado 31-28 parece más complicada?

Listado 31-27Estructura abstracta 1.

XXXXXXXXXXXXXXXXXXX
XXXXX
XXXXXXXXX
XXXXXXXXXXX
XXXXX

Listado 31-28Estructura abstracta 2.

XXXXXXXXXXXXXXXXXXX
XXXXX
XXXXXXXXXX
XXXXXXXXXXXXX
XXXXX

Ambos son representaciones abstractas de la estructura delporcírculo. La Estructura


abstracta 1 parece más complicada aunque representa el mismo código que la
Estructura abstracta 2. Si tuviera que anidar sentencias en dos o tres niveles, la sangría
doble le daría cuatro o seis niveles de sangría. El diseño resultante parecería más
complicado de lo que sería el código real. Evite el problema utilizando la emulación de
bloques puros o utilizandoempezaryfinalcomo límites de bloque y alineaciónempezary
final con las declaraciones que adjuntan.

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:

Usar líneas en blanco entre párrafosAlgunos bloques de código no están delimitados


con principio-finpares Un bloque lógico, un grupo de declaraciones que van juntas, debe
tratarse como lo son los párrafos en inglés. Sepáralos unos de otros con líneas en blanco.
El listado 31-29 muestra un ejemplo de párrafos que deben separarse:
748 Capítulo 31: Diseño y estilo

Listado 31-29Ejemplo de C++ de código que debe agruparse y separarse.

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

se separarán automáticamente. Para


particular, es tentador agruparlas todas juntas de esta manera. No necesita refinar más el
obtener más información, consulte el orden de las declaraciones para la computadora, pero los lectores humanos aprecian más
Capítulo 9, “El proceso de
pistas sobre qué declaraciones deben realizarse en un orden específico y qué declaraciones
programación del pseudocódigo”.
son solo para el viaje. La disciplina de poner líneas en blanco a lo largo de un programa te
hace pensar más sobre qué declaraciones realmente van juntas. El fragmento revisado en el
Listado 31-30 muestra cómo debería organizarse realmente esta colección.

Listado 31-30Ejemplo de C++ de código que está agrupado y separado adecuadamente.

Estas líneas configuran una ventana ventana.dimensiones = editarVentana.dimensiones;


de texto. ventana.titulo = editarVentana.titulo;
ventana.backgroundColor = preferencias de usuario.color de fondo;
w indow.foregroundColor = preferencias de usuario.color de primer plano;

Estas líneas configuran un cursor.start = comenzandoScanLine;


cursor y deben estar separadas cursor.fin = finalizandoScanLine;
de las líneas precedentes. cursor.blinkRate = editMode.blinkRate;
GuardarCursor( cursor );
S etCursor( cursor );

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;
}

Estilo 3 if ( 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.

He usado el Estilo 1 y he sido víctima de modificaciones incorrectas muchas veces. No me gusta la


excepción a la estrategia de sangría causada por el Estilo 3, así que lo evito por completo. En un
proyecto grupal, prefiero cualquiera de las variaciones del Estilo 2 por su consistencia y
modificabilidad segura. Independientemente del estilo que elija, úselo consistentemente y use el
mismo estilo parasipruebas y todos los bucles.

Para expresiones complicadas, coloque condiciones separadas en líneas separadasPon cada


parte de una expresión complicada en su propia línea. El Listado 31-32 muestra una expresión que
está formateada sin prestar atención a la legibilidad:

Listado 31-32Ejemplo de Java de una expresión complicada esencialmente sin formato (e


ilegible).

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

Listado 31-33Ejemplo de Java de una expresión complicada legible.

Referencia cruzadaOtra if ( ( ( '0' <= inChar ) && ( inChar <= '9' ) ) ||


técnica para hacer legibles ( ( 'a' <= inChar ) && ( inChar <= 'z' ) ) || ( ( 'A' <= inChar ) &&
expresiones complicadas ( inChar <= 'Z' ) ) ) . . .
es ponerlas en funciones
booleanas. Para
detalles sobre esa técnica y
otras técnicas de legibilidad, El segundo fragmento utiliza varias técnicas de formato (sangría, espaciado, ordenación de
consulte la Sección 19.1, líneas numéricas y hacer que cada línea incompleta sea obvia) y el resultado es una expresión
“Expresiones booleanas”.
legible. Además, la intención de la prueba es clara. Si la expresión contenía un error menor,
como usar unzen lugar de unZ, sería obvio en el código formateado de esta manera, mientras
que el error no sería claro con un formato menos cuidadoso.

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

número de la tarjeta de crédito.

- 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.

El Listado 31-34 muestra estosirconvenciones de diseño en el trabajo.

Referencia cruzadaPara conocer Listado 31-34Ejemplo en C++ de sacar lo mejor de una mala situación (usandoir).
otros métodos para abordar este

problema, consulte “Procesamiento


void PurgeFiles (código de error y código de error) {
de errores yirs” en la Sección 17.3.
lista de archivos lista de archivos;

int numFilesToPurge = 0;
MakePurgeFileList(fileList, numFilesToPurge);

código de error = FileError_Success; int


archivoÍndice = 0;
while (índice de archivo < numFilesToPurge) {
Archivo de datos para purgar;
if (!FindFile(fileList[fileIndex], fileToPurge)) {
código de error = FileError_NotFound; ir a
Aquí está unir. END_PROC;
}
31.4 Diseño de estructuras de control 751

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;
}

if (! Borrar (archivo para purgar)) {


código de error = FileError_CantErase; ir a
Aquí está unir. END_PROC;
}
archivoÍndice++;
}

Aquí esta lairetiqueta. La intención END_PROC:


de las mayúsculas y el diseño es

hacer que la etiqueta sea difícil de DeletePurgeFileList(fileList, numFilesToPurge);


pasar por alto. }

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.

Listado 31-35Ejemplo de C++ de diseño de línea final difícil de mantener de uncasodeclaración.

cambiar (color de la bola) {


caso BolaColor_Azul: Desenrollar();
descanso;

caso BallColor_Orange: GirarDedo();


descanso;
caso BallColor_FluorescenteVerde: Pico();
descanso;

caso BolaColor_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;

}
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:

Listado 31-36Ejemplo en C++ de buena sangría estándar de uncasodeclaración.

cambiar (color de la bola) {


caso BallColor_Blue:
Desenrollar();
descanso;

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

31.5 Diseño de declaraciones individuales


Esta sección explica muchas formas de mejorar las declaraciones individuales en un programa.

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

"Comentarios de líneas individuales" en la


- Las líneas de más de 80 caracteres son difíciles de leer.
Sección 32.5.

- La limitación de 80 caracteres desalienta el anidamiento profundo.

- 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.

Uso de espacios para mayor claridad

Agregue espacios en blanco dentro de una declaración en aras de la legibilidad:

Use espacios para hacer que las expresiones lógicas sean legiblesLa expresion

while(rutaNombre[rutaInicio+posición]<>';') y
((startPath+posición)<longitud(rutaNombre)) hacer

es tan legible como Idareyoutoreadthis.

Como regla general, debe separar los identificadores de otros identificadores con espacios. Si
utiliza esta regla, eltiempoexpresión se ve así:

while ( rutaNombre[ rutaInicio+posición ] <> ';' ) y


(( rutaInicio + posición ) < longitud( rutaNombre )) hacer

Algunos artistas de software pueden recomendar mejorar esta expresión en particular con
espacios adicionales para enfatizar su estructura lógica, de esta manera:

while (rutaNombre[rutaInicio + posición] <> ';' ) y


((startPath + posición) <longitud(rutaNombre)) hacer

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]

no es más legible que el denso anteriortiempoexpresión. Use espacios alrededor de cada


índice en la matriz para que los índices sean legibles. Si usa esta regla, la expresión se ve
así:

tasa bruta[ censo[ groupId ].género, censo[ 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);

Ahora, ¿cuál es el cuarto argumento de la siguiente rutina?

GetCensus (archivo de entrada, empCount, empData, maxEmps, 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.

Formateo de líneas de continuación


Uno de los problemas más molestos del diseño de un programa es decidir qué hacer con la
parte de una declaración que se extiende a la siguiente línea. ¿Lo sangra por la cantidad de
sangría normal? ¿Lo alineas bajo la palabra clave? ¿Qué pasa con las tareas?

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:

Listado 31-37Ejemplos de Java de declaraciones obviamente incompletas.

los&&indica que la instrucción while (rutaNombre[ rutaInicio + posición ] != ';' ) &&


no está completa. ( ( rutaInicio + posición ) <= rutaNombre.longitud() )
...

El signo más (+) indica que la facturatotal = facturatotal + comprascliente[ IDcliente ] +


declaración no está completa. Impuesto sobre las ventas (compras del cliente [ID del cliente]);
...

La coma (,) indica que la DrawLine( ventana.norte, ventana.sur, ventana.este, ventana.oeste,


instrucción no está completa. anchoActual, AtributoActual );
...
31.5 Diseño de declaraciones individuales 755

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.

Listado 31-38Ejemplos de Java de declaraciones obviamente incompletas: estilo alternativo.

while (rutaNombre[rutainicial + posición]!= ';')


&& ((RutaInicial + posición) <= nombreRuta.longitud() )
...

facturatotal = facturatotal + comprascliente[ IDcliente ]


+ Impuesto sobre las ventas (compras del cliente [ID del cliente]);

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.

Listado 31-39Ejemplo de Java de un estilo que ilumina operaciones complejas.

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:

Listado 31-40Ejemplo de Java de romper una línea mal.

facturaCliente = SaldoAnterior(HistorialPago[IDcliente]) + Cargo Tardío(


historial de pagos [IDcliente]);

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

Listado 31-41Ejemplo de Java de dividir bien una línea.

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:

Listado 31-42Ejemplos de Java de sangría de líneas de continuación de llamada de rutina utilizando el


incremento de sangría estándar.

DrawLine( ventana.norte, ventana.sur, ventana.este, ventana.oeste,


anchoActual, AtributoActual );
SetFontAttributes( faceName[ fontId ], size[ fontId ], bold[ fontId ],
cursiva[ fontId ], syntheticAttribute[ fontId ].subrayado,
syntheticAttribute[ fontId ].tachado );

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.

DrawLine( ventana.norte, ventana.sur, ventana.este, ventana.oeste,


anchoActual, AtributoActual );
SetFontAttributes( faceName[ fontId ], size[ fontId ], bold[ fontId ],
cursiva[ fontId ], syntheticAttribute[ fontId ].subrayado,
syntheticAttribute[ fontId ].tachado );

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
);

Establecer atributos de fuente (

nombre del rostro[ ID de fuente ],


Talla[ ID de fuente ],
audaz[ ID de fuente ],
itálico[ ID de fuente ],
atributo sintético[ ID de fuente ].subrayar,
atributo sintético[ ID de fuente ].tachar
);

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:

Listado 31-45Ejemplos de Java de sangría de líneas de continuación de declaraciones de control.

while ( (rutaNombre[ rutaInicio + posición ] != ';' ) &&


Esta línea de continuación ( ( rutaInicio + posición ) <= rutaNombre.longitud() ) ) { . . .
tiene la sangría estándar.
cantidad de espacios... }

for ( int NúmEmpleado = empleado.primero + empleado.desplazamiento;


. . . como es este. NúmEmpleado < empleado.primero + empleado.compensación + empleado.total;
empleadoNum++ ) {
...
}

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

una función booleana. Para ver


puede hacer de manera consistente: usa solo unos pocos espacios más que la línea original. Es tan legible
ejemplos, consulte “Hacer como cualquier otra cosa, y tan fácil de mantener como cualquier otra cosa. En algunos casos, es posible
Expresiones Complicadas
que pueda mejorar la legibilidad ajustando la sangría o el espaciado, pero asegúrese de tener en cuenta la
Simples” en la Sección 19.1.
compensación de la mantenibilidad cuando considere la posibilidad de realizar un ajuste fino.
758 Capítulo 31: Diseño y estilo

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.

ComprasClientes = ComprasClientes + VentasClientes( IDCliente ); factura del cliente


= facturacliente + comprascliente;
totalFacturaCliente = facturaCliente + SaldoAnterior(IDcliente) +
LateCharge( IDcliente );
Valoración de los clientes = Valoración (IDcliente, FacturaClienteTotal);

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

programa que tienen diferentes niveles de sangría.

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:

Listado 31-47Ejemplo de Java de sangría estándar para la continuación de sentencias de asignación:


buena práctica.

ComprasClientes = ComprasClientes + VentasClientes( IDCliente ); facturacliente =


facturacliente + comprascliente;
totalFacturaCliente = facturaCliente + SaldoAnterior(IDcliente) +
LateCharge( IDcliente );
ValoraciónCliente = Valoración( IDCliente, FacturaClienteTotal );

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.

Usando solo una declaración por línea


Los lenguajes modernos como C++ y Java permiten múltiples declaraciones por línea. Sin
embargo, el poder del formato libre es una bendición mixta cuando se trata de poner varias
declaraciones en una línea. Esta línea contiene varias declaraciones que lógicamente podrían
separarse en líneas propias:

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

Capítulo 25, "Estrategias de ajuste


pistas de formato para realizar sus optimizaciones. Esto se ilustra más adelante en esta sección.
de código" y el Capítulo 26,
- Con declaraciones en sus propias líneas, el código se lee de arriba a abajo, en lugar de arriba a
"Técnicas de ajuste de código".
abajo y de izquierda a derecha. Cuando esté buscando una línea de código específica, su ojo
debería poder seguir el margen izquierdo del código. No debería tener que sumergirse en
todas y cada una de las líneas solo porque una sola línea puede contener dos declaraciones.

- 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?

Listado 31-48Ejemplo en C++ de un efecto secundario impredecible.

ImprimirMensaje( ++n, n + 2 );
760 Capítulo 31: Diseño y estilo

Lo es4y6? Lo es5y7? Lo es5y6? La respuesta es “Ninguna de las anteriores”. El primer argumento,++


norte, es5. Pero el lenguaje C++ no define el orden en que se evalúan los términos de una expresión
o los argumentos de una rutina. Entonces el compilador puede evaluar el segundo argumento,norte
+ 2, ya sea antes o después del primer argumento; el resultado puede ser cualquiera6o7,
dependiendo del compilador. El Listado 31-49 muestra cómo debe reescribir la declaración para que
la intención sea clara:

Listado 31-49Ejemplo de C++ de cómo evitar un efecto secundario impredecible.

+ + 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:

Listado 31-50C ejemplo de demasiadas operaciones en una línea.

strcpy( char * t, char * s ) {


mientras ( *++t = *++s )
;
}

Algunos programadores de C experimentados no ven la complejidad en ese ejemplo porque es


una función familiar. Lo miran y dicen: "Eso esstrcpy().” En este caso, sin embargo, no es del
todostrcpy(). Contiene un error. Si dijiste: “Eso esstrcpy()” cuando vio el código, estaba
reconociendo el código, no leyéndolo. Esta es exactamente la situación en la que se encuentra
cuando depura un programa: el código que pasa por alto porque lo "reconoce" en lugar de
leerlo puede contener el error que es más difícil de encontrar de lo necesario.

El fragmento que se muestra en el Listado 31-51 es funcionalmente idéntico al primero y es más


legible:

Listado 31-51C ejemplo de un número legible de operaciones en cada línea.

strcpy( char * t, char * s ) {


hacer {
+ + t;
+ + s;
* t = *s;
}
mientras (*t!= '\0');
}

En el código reformateado, el error es evidente. Claramente,tysse incrementan antes


* sse copia a*t. Se pierde el primer carácter.

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

Capítulo 25, "Estrategias de ajuste de

código", y el Capítulo 26,


un código idéntico para ellas. Sin embargo, cuando se perfilaron ambas versiones de la rutina,
"Técnicas de ajuste de código". la primera versión tardó 4,81 segundos en copiar 5 000 000 de cadenas y la segunda tardó
4,35 segundos.

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.

Diseño de declaraciones de datos


Referencia cruzadaPara obtener detalles Use solo una declaración de datos por líneaComo se muestra en los ejemplos anteriores, debe dar
sobre la documentación de las
a cada declaración de datos su propia línea. Es más fácil poner un comentario al lado de cada
declaraciones de datos, consulte

"Declaraciones de datos de comentarios"


declaración si cada una está en su propia línea. Es más fácil modificar declaraciones porque cada
en la Sección 32.5. Para aspectos del uso declaración es independiente. Es más fácil encontrar variables específicas porque puede escanear
de datos, consulte los Capítulos 10 a 13.
una sola columna en lugar de leer cada línea. Es más fácil encontrar y corregir errores de sintaxis
porque el número de línea que le da el compilador tiene solo una declaración.

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.

int índice_fila, idx_columna;


Color color anterior, color actual, color siguiente;
Punto anteriorSuperior, anteriorInferior, actualSuperior, actualInferior, siguienteSuperior,
CODIFICACIÓN siguienteInferior;
HORROR
Fuente tipo de letra anterior, tipo de letra actual, tipo de letra siguiente;
Opciones de color[ NUM_COLORS ];
762 Capítulo 31: Diseño y estilo

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;

Opciones de color[ NUM_COLORS ];

La variablesiguienteColorprobablemente fue más fácil de encontrar quesiguienteTipo de letraestaba


en el Listado 31-53. Este estilo se caracteriza por una declaración por línea y una declaración
completa, incluido el tipo de variable, en cada línea.

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.

En C++, coloque el asterisco junto al nombre de la variable en declaraciones de puntero o


declare tipos de punteroEs común ver declaraciones de puntero que colocan el asterisco al lado
del tipo, como en el Listado 31-55:

Listado 31-55Ejemplo en C++ de asteriscos en declaraciones de puntero.

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:

Listado 31-56Ejemplo de C++ del uso de asteriscos en declaraciones de puntero.

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.

El mejor enfoque es declarar un tipo para el puntero y usarlo en su lugar. Un ejemplo se


muestra en el Listado 31-57:

Listado 31-57Ejemplo en C++ de buenos usos de un tipo de puntero en declaraciones.

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!

31.6 Diseño de comentarios


Referencia cruzadaPara obtener detalles Los comentarios bien hechos pueden mejorar en gran medida la legibilidad de un programa; los comentarios mal
sobre otros aspectos de los comentarios,
hechos en realidad pueden dañarlo. El diseño de los comentarios juega un papel importante en si ayudan o
consulte el Capítulo 32, “Código de

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?

Listado 31-58Ejemplo de Visual Basic de comentarios mal sangrados.

For transactionId = 1 To totalTransactions ' obtener datos


de transacciones
ObtenerTipoTransacción( tipo de transacción )
CODIFICACIÓN ObtenerImporteTransacción( cantidad de transacción )
HORROR

'procesar la transacción según el tipo de transacción


Si tipo de transacción = Transaction_Sale Entonces
AceptarVentaCliente(cantidadtransacción)

Más
Si tipo de transacción = Transaction_CustomerReturn Entonces

' procesar la devolución automáticamente u obtener la aprobación del gerente, si es necesario


Si cantidad de transacción >= MANAGER_APPROVAL_LEVEL 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 End If )

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

Listado 31-59Ejemplo de Visual Basic de comentarios con buena sangría.

Para transacciónId = 1 Para transacciones totales


' Obtener datos de transacción
GetTransactionType(TransactionType)
GetTransactionAmount(TransactionAmount )

' procesar la transacción en función del tipo de transacción If


transactionType = Transaction_Sale Then
AceptarVentaCliente(cantidadtransacción)

Más
Si tipo de transacción = Transaction_CustomerReturn Entonces

' procesar la devolución automáticamente u obtener la aprobación del gerente, si es necesario


Si cantidad de transacción >= MANAGER_APPROVAL_LEVEL 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

En el Listado 31-59, la estructura lógica es más evidente. Un estudio sobre la efectividad


de los comentarios encontró que el beneficio de tener comentarios no era concluyente, y
el autor especuló que se debía a que "interrumpían el escaneo visual del
programa" (Shneiderman 1980). A partir de estos ejemplos, es obvio que elestilode
comentar influye fuertemente en si los comentarios son perjudiciales.

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:

Listado 31-60Ejemplo de Java de iniciar un comentario con una línea en blanco.

// 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:

Listado 31-61Ejemplo de Java de iniciar un comentario con dos líneas en blanco.

// 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.

31.7 Diseño de rutinas


Referencia cruzadaPara obtener Las rutinas se componen de sentencias individuales, datos, estructuras de control,
detalles sobre la documentación de
comentarios, todo lo discutido en las otras partes del capítulo. Esta sección proporciona pautas
las rutinas, consulte "Rutinas de

comentarios" en la Sección 32.5. Para


de diseño exclusivas para las rutinas.
obtener detalles sobre el proceso de

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

discusión de las diferencias


Usar sangría estándar para argumentos de rutinaLas opciones con el diseño de encabezado de
entre buenas y malas rutinas,
rutina son casi las mismas que en muchas otras áreas de diseño: sin diseño consciente, diseño de
consulte el Capítulo 7, “Rutinas
de alta calidad”. línea final o sangría estándar. Como en la mayoría de los otros casos, la sangría estándar funciona
mejor en términos de precisión, consistencia, legibilidad y modificabilidad. El listado 31-62 muestra
dos ejemplos de encabezados de rutina sin diseño consciente:

Listado 31-62Ejemplos de C++ de encabezados de rutina sin diseño consciente.

bool ReadEmployeeData(int maxEmployees,EmployeeList *empleados,


EmployeeFile *inputFile,int *employeeCount,bool *isInputError)
...

void InsertionSort (SortArray data, int firstElement, int lastElement)

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.

bool Leer datos de empleados ( En t maxEmpleados,


Lista de empleados * empleados,
EmpleadoArchivo * fichero de entrada,

En t * número de empleados,

bool * es un error de entrada)

...
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 parámetros de rutina”.


el nombre de la función cambia deLeer datos de empleados ()aLeerNuevoEmplear- eeData(). Eso
Parámetros.” desviaría la alineación de la primera línea de la de las otras cuatro líneas. Tendría que reformatear las
otras cuatro líneas de la lista de parámetros para alinearlas con la nueva posición demaxEmpleados
causado por el nombre de función más largo. Y probablemente se quede sin espacio en el lado
derecho ya que los elementos ya están muy a la derecha.

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.

Listado 31-64Ejemplo de C++ de encabezados de rutina con sangría estándar legible y


mantenible.

público bool LeerDatosEmpleado(


En t maxEmpleados,
Lista de empleados * empleados,
EmpleadoArchivo * fichero de entrada,

En t * número de empleados,
bool * es un error de entrada

)
...

vacío público Tipo de inserción(


OrdenarArray datos,
En t primer elemento,
En t último elemento
)

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.

Aquí está el carácter “_” utilizado Public Sub ReadEmployeeData ( _


como carácter de continuación de ByVal maxEmployees As Integer, _ ByRef
línea. empleados As EmployeeList, _ ByRef inputFile
As EmployeeFile, _ ByRef employeeCount As
Integer, _ ByRef isInputError As Boolean _

31.8 Disposición de Clases


Esta sección presenta pautas para diseñar el código en las clases. La primera subsección
describe cómo diseñar la interfaz de clase. La segunda subsección describe cómo diseñar las
implementaciones de clase. La subsección final analiza la distribución de archivos y
programas.

Diseño de interfaces de clase


Referencia cruzadaPara obtener más Al diseñar las interfaces de clase, la convención es presentar los miembros de la clase en el
información sobre la documentación
siguiente orden:
de clases, consulte "Comentar clases,

archivos y programas" en la Sección


1.Comentario de encabezado que describe la clase y proporciona notas sobre el uso
32.5. Para una discusión de las

diferencias entre las clases buenas y general de la clase


malas, vea el Capítulo 6, “Clases

trabajadoras”.
2.Constructores y destructores
3.Rutinas públicas
4.rutinas protegidas
5.Rutinas privadas y datos de miembros

Diseño de implementaciones de clase


Las implementaciones de clase generalmente se presentan en este orden:

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:

Listado 31-66Ejemplo en C++ de cómo formatear la separación entre clases.

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';
}

El comienzo de la nueva clase //------------------------------------------------ ---------------------- //


se marca con varias líneas en FUNCIONES MATEMÁTICAS
blanco y el nombre de la clase. //
// Esta clase contiene las funciones matemáticas del programa. //------------------------------------------------
----------------------

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:

Listado 31-67Ejemplo en C++ de sobreformatear una clase.

//**************************************************** ******************** //
**************************** ******************************************* //
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

Listado 31-68Ejemplo en C++ de buen formateo con moderación.

//**************************************************** ********************** //
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á

subordinada a la clase. 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;
}
}

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í.

Diseño de archivos y programas


Referencia cruzadaPara obtener Más allá de las técnicas de formato para las clases, hay un problema de formato más amplio: ¿cómo
detalles sobre la documentación,
organiza las clases y las rutinas dentro de un archivo y cómo decide qué clases colocar en un archivo
consulte "Comentar clases, archivos y

programas" en la Sección 32.5.


en primer lugar?

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

de cliente tendría archivos llamadosCuentaCliente.cppyCuentaCliente.h, por ejemplo.

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.

'encontrar el máximo aritmético de arg1 y arg2


Función Max (arg1 como entero, arg2 como entero) como entero
Si (arg1 > arg2) Entonces
máx. = arg1
Más
máx. = arg2
Terminara si

función final
Al menos dos líneas en blanco
separar las dos rutinas.

'encontrar el mínimo aritmético de arg1 y arg2


Función Min(arg1 como entero, arg2 como entero) como entero
Si ( arg1 < arg2 ) Entonces
mínimo = arg1
Más
mínimo = arg2
Terminara si

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.

Rutinas de secuencia alfabéticamenteUna alternativa a agrupar rutinas relacionadas en un archivo


es ponerlas en orden alfabético. Si no puede dividir un programa en clases o si su editor no le
permite encontrar funciones fácilmente, el enfoque alfabético puede ahorrarle tiempo de búsqueda.
31.8 Disposición de Clases 773

En C++, ordene el archivo fuente con cuidadoEste es un orden típico del contenido del archivo fuente en
C++:

1.Comentario de descripción de archivo

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)

5.Definiciones de funciones de macros

6.Escriba definiciones que se apliquen a más de una clase (si hay más de una clase en el archivo)

7.Variables globales y funciones importadas

8.Variables globales y funciones exportadas

9.Variables y funciones que son privadas para el archivo

10Clases, incluidas definiciones constantes, enumeraciones y definiciones de tipo dentro de cada


clase

cc2e.com/3194 LISTA DE VERIFICACIÓN: Diseño

General
- ¿El formateo se realiza principalmente para iluminar la estructura lógica del
código?

- ¿Se puede usar el esquema de formato de manera consistente?

- ¿El esquema de formato da como resultado un código que es fácil de mantener?

- ¿El esquema de formato mejora la legibilidad 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?

- ¿Las expresiones complicadas están formateadas para facilitar la lectura?

- ¿Los bloques de una sola declaración están formateados de manera consistente?

- Soncasodeclaraciones formateadas de una manera que es consistente con el formato


de otras estructuras de control?

- Tenerir¿Ha sido formateado de una manera que hace que su uso sea obvio?
Traducido del inglés al español - www.onlinedoctranslator.com

774 Capítulo 31: Diseño y estilo

Estados de cuenta individuales

- ¿Se utilizan espacios en blanco para hacer legibles las expresiones lógicas, las referencias de matriz y

los argumentos de rutina?

- ¿Las declaraciones incompletas terminan la línea de una manera que es obviamente incorrecta?

- ¿Las líneas de continuación tienen la sangría estándar?


- ¿Cada línea contiene como máximo una afirmación?

- ¿Cada declaración está escrita sin efectos secundarios?

- ¿Hay como máximo una declaración de datos por línea?

Comentarios
- ¿Los comentarios tienen la misma sangría de espacios que el código que
comentan?

- ¿El estilo de comentarios es fácil de mantener?

Rutinas
- ¿Los argumentos de cada rutina están formateados de modo que cada argumento sea
fácil de leer, modificar y comentar?

- ¿Se utilizan líneas en blanco para separar partes de una rutina?

Clases, Archivos y Programas


- ¿Existe una relación de uno a uno entre clases y archivos para la mayoría de las clases y
archivos?

- Si un archivo contiene múltiples clases, ¿están agrupadas todas las rutinas de


cada clase y cada clase está claramente identificada?

- ¿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:

Kernighan, Brian W. y Rob Pike.La práctica de la programaciónReading, MA: Addison-


Wesley, 1999. El capítulo 1 de este libro analiza el estilo de programación centrándose en
C y C++.
Puntos clave 775

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.

Para un enfoque sustancialmente diferente de la legibilidad, eche un vistazo al siguiente libro:

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

- 32.1 Documentación externa: página 777

- 32.2 Estilo de programación como documentación: página 778

- 32.3 Comentar o no comentar: página 781


- 32.4 Claves para comentarios efectivos: página 785

- 32.5 Técnicas de comentarios: página 792

- 32.6 Estándares IEEE: página 813

Temas relacionados

- Diseño: Capítulo 31

- El proceso de programación del pseudocódigo: Capítulo 9

- Clases obreras: Capítulo 6

- Rutinas de alta calidad: Capítulo 7

- Programación como comunicación: Secciones 33.5 y 34.3

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".

32.1 Documentación externa


Referencia cruzadaPara obtener más La documentación de un proyecto de software consta de información tanto dentro de las listas de código
información sobre la documentación
fuente como fuera de ellas, generalmente en forma de documentos separados o carpetas de desarrollo de
externa, consulte la Sección 32.6,

"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".

Folder (UDF): una herramienta de


construcción. Una "unidad" se define vagamente, generalmente para referirse a una clase, aunque también
gestión eficaz para el desarrollo podría significar un paquete o un componente. El objetivo principal de una UDF es proporcionar un
de software” (Ingrassia 1976) o
seguimiento de las decisiones de diseño que no están documentadas en ningún otro lugar. Muchos
“The Unit
proyectos tienen estándares que especifican el contenido mínimo de un FDU, como copias de los requisitos
Carpeta de Desarrollo (UDF): Una

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.

Documento de diseño detalladoEl documento de diseño detallado es el documento de


diseño de bajo nivel. Describe las decisiones de diseño a nivel de clase o de rutina, las
alternativas que se consideraron y las razones para seleccionar los enfoques que se
seleccionaron. A veces esta información está contenida en un documento formal. En tales
casos, el diseño detallado generalmente se considera separado de la construcción. A veces
consiste principalmente en notas de desarrolladores recopiladas en una UDF. Y a veces, a
menudo, solo existe en el código mismo.

32.2 Estilo de programación como documentación


A diferencia de la documentación externa, la documentación interna se encuentra dentro de la
propia lista de programas. Es el tipo de documentación más detallado, a nivel de declaración de
origen. Debido a que está más estrechamente asociado con el código, la documentación interna
también es el tipo de documentación que probablemente permanecerá correcta a medida que se
modifica el código.

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.

Aquí hay un fragmento de código con un estilo deficiente:

Ejemplo de Java de documentación deficiente resultante de un estilo de programación incorrecto


for ( i = 2; i <= num; i++ )
{ cumpleCriterio[ i ] = verdadero; }
CODIFICACIÓN

HORROR
for ( i = 2; i <= num / 2; i++ ) { j = i + i;

while ( j <= número ) {


32.2 Estilo de programación como documentación 779

cumple con los criterios[ j ] = false; j =


j + yo;
}
}
for ( i = 2; i <= num; i++ ) { if ( cumpleCriterio[ i ] )
{ System.out.println ( i + " cumple criterio." ); }

¿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:

Ejemplo Java de Documentación Sin Comentarios (con Buen Estilo)


for (candidatoprincipal = 2; candidatoprincipal <= num;candidatoprincipal++) {
isPrime[ primeCandidate ] = verdadero;
}

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>

operación. Para obtener detalles

sobre cómo agregar variables para factorableNumber + factor;


aclarar las operaciones, consulte
}
"Hacer simples las expresiones
}
complicadas" en la Sección 19.1.
for (candidatoprincipal = 2; candidatoprincipal <= num;candidatoprincipal++) {
if (esPrimo[principalCandidato]) {
System.out.println( primeCandidate + " es primo." );
}
}

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

cc2e.com/3252 LISTA DE VERIFICACIÓN: Código de autodocumentación

Clases
- ¿La interfaz de la clase presenta una abstracción consistente?

- ¿La clase está bien nombrada y su nombre describe su propósito central?

- ¿La interfaz de la clase deja en claro cómo debe usar la clase?


- ¿Es la interfaz de la clase lo suficientemente abstracta como para no tener que pensar
en cómo se implementan sus servicios? ¿Puedes tratar la clase como una caja negra?

Rutinas
- ¿El nombre de cada rutina describe exactamente lo que hace la rutina?

- ¿Cada rutina realiza una tarea bien definida?


- ¿Se han incluido en sus propias rutinas todas las partes de cada rutina que se
beneficiarían si se incluyeran en sus propias rutinas?

- ¿La interfaz de cada rutina es obvia y clara?

Nombres de datos

- ¿Son los nombres de tipos lo suficientemente descriptivos para ayudar a documentar las declaraciones de datos?

- ¿Están bien nombradas las variables?

- ¿Las variables se usan solo para el propósito para el que se nombran?

- ¿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?

- ¿Las convenciones de nomenclatura distinguen entre nombres de tipo, tipos enumerados,


constantes con nombre, variables locales, variables de clase y variables globales?

Organización de datos
- ¿Se utilizan variables adicionales para mayor claridad cuando es necesario?

- ¿Las referencias a las variables están muy juntas?

- ¿Los tipos de datos son simples para minimizar la complejidad?

- ¿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?

- ¿Están agrupadas las declaraciones relacionadas?


32.3 Comentar o no comentar 781

- ¿Se han empaquetado grupos de sentencias relativamente independientes en sus


propias rutinas?

- ¿El caso normal sigue elsien lugar de lamás?


- ¿Las estructuras de control son simples para minimizar la complejidad?

- ¿Cada bucle realiza una y sólo una función, como lo haría una rutina bien
definida?

- ¿Se minimiza el anidamiento?

- ¿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?

32.3 Comentar o no comentar


Los comentarios son más fáciles de escribir mal que bien, y comentar puede ser más dañino
que útil. Las acaloradas discusiones sobre las virtudes de comentar a menudo suenan como
debates filosóficos sobre las virtudes morales, lo que me hace pensar que si Sócrates hubiera
sido programador de computadoras, él y sus alumnos podrían haber tenido la siguiente
discusión.

El comentario
Caracteres:

TRASÍMACO Un purista verde y teórico que se cree todo lo que lee.

CALÍCULAS Un veterano curtido en batallas de la vieja escuela: un programador "real"

GLAUCON Un deportista joven, confiado y experto en computadoras.

ISMENE Un programador senior cansado de grandes promesas, solo busca algunas prácticas que
funcionen

SÓCRATES El viejo y sabio programador


782 Capítulo 32: Código de autodocumentación

Ajuste:

FIN DE LA REUNIÓN DIARIA DE STANDUP DEL EQUIPO

"¿Alguien tiene algún otro problema antes de que volvamos al trabajo?" preguntó Sócrates.

“Quiero sugerir un estándar de comentarios para nuestros proyectos”, dijo Thrasymachus.


“Algunos de nuestros programadores apenas comentan su código, y todos saben que el
código sin comentarios es ilegible”.

“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…”

“Espera ahí”, interrumpió Thrasymachus. “Los buenos comentarios no repiten el código ni lo


explican. Aclaran su intención. Los comentarios deben explicar, en un nivel más alto de
abstracción que el código, lo que estás tratando de hacer".
PUNTO CLAVE

"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.

“Diseñe rutinas en pseudocódigo y luego convierta el pseudocódigo en comentarios y


complete el código entre ellos”, dijo Glaucon.

“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”.

32.4 Claves para comentarios efectivos


Siempre que haya objetivos mal ¿Qué hace la siguiente rutina?
definidos, errores extraños y

cronogramas poco realistas,

habrá programadores reales Java misterio rutina número uno


dispuestos a intervenir y resolver // escribe las sumas 1..n para todos los n desde 1 hasta num actual =
el problema, guardando la 1;
documentación para más tarde. anterior = 0;
¡Viva Fortran! suma = 1;
— Publicación de Ed. para ( int i = 0; i < número; i++ ) {
System.out.println( "Suma = " + suma ); suma =
actual + anterior;
anterior = corriente;
Actual = suma;
}

¿Tu mejor suposición?

Esta rutina calcula la primeranúmeronúmeros de Fibonacci. Su estilo de codificación es un poco


mejor que el estilo de la rutina al principio del capítulo, pero el comentario está mal, y si confías
ciegamente en el comentario, vas por el camino de la primavera en la dirección equivocada.

¿Qué hay de este?

Java misterio rutina número dos


// establece el producto en
"base" producto = base;

// 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 genera un número enterobasea la potencia enteranúmero. Los comentarios en


esta rutina son precisos, pero no agregan nada al código. Son simplemente una versión más
detallada del código en sí.

Aquí hay una última rutina:

Java misterio rutina número tres


// calcular la raíz cuadrada de Num utilizando la aproximación de Newton-Raphson r = num / 2;

while ( abs( r - (num/r) ) > TOLERANCIA ) {


r = 0,5 * ( r + (núm/r) );
}
Sistema.salida.println( "r = " + r );

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:

Repetición del Código

Un comentario repetitivo reafirma lo que hace el código en diferentes palabras. Simplemente


le da al lector del código más para leer sin proporcionar información adicional.

Explicación del Código


Los comentarios explicativos generalmente se usan para explicar piezas de código
complicadas, engañosas o sensibles. En tales situaciones, son útiles, pero generalmente eso
es solo porque el código es confuso. Si el código es tan complicado que necesita ser explicado,
casi siempre es mejor mejorar el código que agregar comentarios. Haga que el código en sí
sea más claro y luego use comentarios de resumen o intención.
32.4 Claves para comentarios efectivos 787

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:

devuelve NULL; // ****** ¡NO HECHO! ARREGLAR ANTES DE LIBERAR!!!

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.

Resumen del Código


Un comentario que resume el código hace exactamente eso: destila unas pocas líneas de código en
una o dos oraciones. Dichos comentarios son más valiosos que los comentarios que simplemente
repiten el código porque un lector puede escanearlos más rápidamente que el código. Los
comentarios de resumen son particularmente útiles cuando alguien que no es el autor original del
código intenta modificar el código.

Descripción de la Intención del Código

Un comentario a nivel de intención explica el propósito de una sección de código. Los


comentarios de intención operan más al nivel del problema que al nivel de la solución. Por
ejemplo,

- - obtener información actual del empleado

es un comentario intencional, mientras que

- - actualizar objeto employeeRecord


788 Capítulo 32: Código de autodocumentación

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.

Información que posiblemente no pueda ser expresada por el propio código

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.

Las siguientes son pautas para comentar de manera eficiente:

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á:

Ejemplo de Java de un estilo de comentarios que es difícil de mantener


// Variable Sentido
// -------- -------
// xPos .......... Posición de la coordenada X (en metros) yPos ..........
// Posición de la coordenada Y (en metros)
32.4 Claves para comentarios efectivos 789

// ndsCmptng...... Needs Computing (= 0 si no se necesita ningún cálculo,


// = 1 si se necesita cálculo)
// ptGrdTtl....... Total general de puntos ptValMax.......
// Valor máximo de puntos psblScrMax..... Puntaje
// máximo posible

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.

Aquí hay otro ejemplo de un estilo común que es difícil de mantener:

Ejemplo en C++ de un estilo de comentarios que es difícil de mantener


/**************************************************** ********************
* clase: GigaTron (GIGATRON.CPP) *
* *
* autor: Dwight K. Codificador *
* fecha: 4 de julio de 2014 *
* *
* Rutinas para controlar la evaluación del código del siglo XXI *
* herramienta. El punto de entrada a estas rutinas es EvaluateCode() *
* rutina en la parte inferior de este archivo. *
*********************************************************************/

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:

Ejemplo en C++ de un estilo de comentarios que es fácil de mantener


/**************************************************** ********************
clase: GigaTron (GIGATRON.CPP)

autor: Dwight K. Codificador


fecha: 4 de julio de 2014

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

Aquí hay un estilo particularmente difícil de mantener:

Ejemplo de Microsoft Visual Basic de un estilo de comentarios que es difícil de mantener


' configurar el tipo enumerado de color
' +----------------------------+
CODIFICACIÓN ...
HORROR

' configurar el tipo enumerado vegetal


' +---------------------------------+
...

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

Capítulo 9, "El proceso de


comentarios están listos. No tienes que dedicar tiempo a los comentarios. También obtiene todos los
programación del pseudocódigo". beneficios de diseño de escribir en pseudocódigo de alto nivel antes de completar el código de lenguaje de
programación de bajo nivel.

Integre los comentarios en su estilo de desarrolloLa alternativa a integrar los


comentarios en tu estilo de desarrollo es dejar los comentarios para el final del proyecto,
y eso tiene demasiadas desventajas. Se convierte en una tarea por derecho propio, lo
que hace que parezca más trabajo que cuando se hace poco a poco. Comentar más tarde
lleva más tiempo porque tienes que recordar o descubrir qué está haciendo el código en
lugar de simplemente escribir lo que ya estás pensando. También es menos preciso
porque tiende a olvidar suposiciones o sutilezas en el diseño.

El argumento común en contra de comentar sobre la marcha es "Cuando te concentras en el


código, no debes interrumpir tu concentración para escribir comentarios". La respuesta
apropiada es que, si tiene que concentrarse tanto en escribir código que los comentarios
interrumpen su pensamiento, primero debe diseñar en pseudocódigo y luego convertir el
pseudocódigo en comentarios. El código que requiere tanta concentración es una señal de
advertencia.

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

Número óptimo de comentarios


3 Capers Jones señala que los estudios en IBM encontraron que una densidad de comentarios de un comentario
2
1
aproximadamente cada 10 declaraciones era la densidad en la que la claridad parecía alcanzar su punto máximo.
Menos comentarios hicieron que el código fuera difícil de entender. Más comentarios también redujeron la
DATOS DUROS

comprensibilidad del código (Jones 2000).

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.

Si usa el proceso de programación de pseudocódigo de manera efectiva, probablemente terminará con un


comentario por cada pocas líneas de código. Sin embargo, la cantidad de comentarios será un efecto
secundario del proceso en sí. En lugar de centrarse en la cantidad de comentarios, concéntrese en si cada
comentario es eficiente. Si los comentarios describen por qué se escribió el código y cumplen con los otros
criterios establecidos en este capítulo, tendrá suficientes comentarios.

32.5 Técnicas de comentarios


Los comentarios se pueden aplicar a varias técnicas diferentes según el nivel al que se
aplican los comentarios: programa, archivo, rutina, párrafo o línea individual.

Comentar líneas individuales


En un buen código, la necesidad de comentar líneas individuales de código es rara. Aquí hay dos posibles razones

por las que una línea de código necesitaría un comentario:

- La sola línea es lo suficientemente complicada como para necesitar una explicación.

- La línea única una vez tuvo un error y desea un registro del error.

Aquí hay algunas pautas para comentar una línea de código:

Evite los comentarios autoindulgentesHace muchos años, escuché la historia de un programador de


mantenimiento que fue llamado a levantarse de la cama para arreglar un programa que no funcionaba
bien. El autor del programa había dejado la empresa y no podía ser localizado. El programador de
mantenimiento no había trabajado antes en el programa y, después de examinar la documentación
detenidamente, solo encontró un comentario. Se veía así:

HACHA MOV, 723h ; RIPLVB

Después de trabajar con el programa toda la noche y de pensar en el comentario, el


programador hizo un parche exitoso y se fue a la cama. Meses después, conoció al autor del
programa en una conferencia y descubrió que el comentario significaba “Descansa en paz,
Ludwig van Beethoven”. Beethoven murió en 1827 (decimal), que es 723 (hexadecimal). El
hecho de que se necesitaran 723h en ese lugar no tiene nada que ver con el comentario.
¡Aaarrrrghhhhh!
32.5 Técnicas de comentarios 793

Comentarios finales y sus problemas


Los comentarios finales son comentarios que aparecen al final de las líneas de código:

Ejemplo de Visual Basic de comentarios finales


For employeeId = 1 Para employeeCount
GetBonus(IdEmpleado, TipoEmpleado, MontoBonificación) Si
TipoEmpleado = TipoEmpleado_Gestor Entonces
PayManagerBonus( employeeId, bonusAmount ) ' paga el monto total Else

Si tipoEmpleado = TipoEmpleado_Programador Entonces


Si bonusAmount >= MANAGER_APPROVAL_LEVEL Entonces
PayProgrammerBonus( employeeId, StdAmt() ) ' pay std. cantidad más

PayProgrammerBonus( employeeId, bonusAmount ) ' paga el monto total End If

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:

Ejemplo de C++ de comentarios de línea final inútiles


Los comentarios simplemente memoriaParaInicializar = MemoriaDisponible(); // obtener la cantidad de memoria disponible
repite el código. puntero = GetMemory( memoryToInitialize ); // obtener un ptr de la memoria disponible
ZeroMemory( pointer, memoryToInitialize ); // establece la memoria en 0
...
FreeMemory(puntero); // memoria libre asignada
794 Capítulo 32: Código de autodocumentación

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:

Ejemplo de Visual Basic de un comentario final confuso en varias líneas de código


Para rateIdx = 1 a rateCount ' Calcular tarifas con descuento
LookupRegularRate( rateIdx, regularRate )
CODIFICACIÓN tasa( tasaIdx ) = tasaregular * descuento( tasaIdx ) Siguiente
HORROR

Aunque el contenido de este comentario en particular está bien, su ubicación no lo está.


Debe leer el comentario y el código para saber si el comentario se aplica a una
declaración específica o al ciclo completo.

Cuándo usar comentarios finales

Considere tres excepciones a la recomendación contra el uso de comentarios finales:

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

datos se describen en "Declaraciones


comentarios finales en el código, siempre que tenga suficiente ancho. Con 132 columnas,
de datos de comentarios", más normalmente puede escribir un comentario significativo al lado de cada declaración de datos:
adelante en esta sección.

Ejemplo de Java de buenos comentarios finales para declaraciones de datos


límite int = 0; // índice superior de la parte ordenada de la matriz
Cadena insertVal = EN BLANCO; // datos elmt para insertar en la parte ordenada de la matriz int
insertPos = 0; // posición para insertar elmt en la parte ordenada de la matriz

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:

para i = 1 a maxElmts – 1 - - error fijo #A423 1/10/05 (scm)

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

con más detalle en “Comentar


ejemplo. Esto se describe con más detalle más adelante en este capítulo.
Estructuras de control”, más adelante en

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.

Comentar párrafos del Código


La mayoría de los comentarios en un programa bien documentado son comentarios de una o
dos oraciones que describen párrafos de código:

Ejemplo Java de un buen comentario para un párrafo de código


// intercambiar las raíces
oldRoot = raíz[0];
raíz[0] = raíz[1];
raíz[1] = antiguaRaíz;

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.

Escriba comentarios al nivel de la intención del código.Describa el propósito del bloque de


código que sigue al comentario. Aquí hay un ejemplo de un comentario que no es efectivo
porque no opera en el nivel de intención:

Ejemplo Java de un comentario ineficaz


Referencia cruzadaEste código que /* comprobar cada carácter en "inputString" hasta que se encuentre un signo de
realiza una búsqueda de cadena dólar o se hayan comprobado todos los caracteres
simple se usa solo con fines */
ilustrativos. Para el código real, usaría hecho = falso;
las funciones de biblioteca de maxLen = inputString.longitud();
cadenas integradas de Java en su yo = 0;
lugar. Para obtener más información while ( ! hecho && ( i < maxLen ) ) {
sobre la importancia de comprender if ( cadena de entrada [ i ] == '$' ) {
las capacidades de su idioma, hecho = verdadero;
consulte "¡Leer!" en la Sección 33.3. }
más {
yo++;
}
}

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:

// encuentra '$' en inputString

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:

// encuentra el terminador de palabra de comando ($)

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.

Concentre sus esfuerzos de documentación en el código mismoPara que conste, el código


en sí es siempre la primera documentación que debe consultar. En el ejemplo anterior, el
literal, ps, debe reemplazarse con una constante con nombre y las variables deben
PUNTO CLAVE
proporcionar más pistas sobre lo que está sucediendo. Si desea empujar el borde del sobre de
legibilidad, agregue una variable para contener el resultado de la búsqueda. Hacer eso
distingue claramente entre el índice del ciclo y el resultado del ciclo. Aquí está el código
reescrito con buenos comentarios y buen estilo:

Ejemplo de Java de un buen comentario y un buen código


// encuentra el terminador de la palabra de
= falso;
comando foundTheTerminator
comandoStringLength = inputString.longitud();
testCharPosition = 0;
while (!foundTheTerminator && (testCharPosition <comandoStringLength)) {
if (CadenaEntrada[TestCharPosition] == COMMAND_WORD_TERMINATOR) {
encontrado el terminador = verdadero;
Aquí está la variable que TerminatorPosition = testCharPosition;
contiene el resultado de la }
búsqueda. más {
testCharPosition = testCharPosition + 1;
}
}
32.5 Técnicas de comentarios 797

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,

consulte "Extraer rutina/método de


comentario que describe esa idea es útil, pero es más probable que el nombre de una
extracción" en la Sección 24.3. rutina se vuelva inexacto a medida que el software evoluciona.

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?

Ejemplo en Java de un comentario que se enfoca enCómo


// si el indicador de cuenta es cero if
( indicador de cuenta == 0 ) ...
CODIFICACIÓN

HORROR

El comentario no le dice nada más que el propio código. ¿Qué pasa con este
comentario?

Ejemplo en Java de un comentario que se enfoca enPor qué


// si se establece una nueva cuenta if
( accountFlag == 0 ) ...

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í:

Ejemplo de Java del uso de un comentario de "encabezado de sección"


// establecer una nueva cuenta
if (tipoCuenta == TipoCuenta.NuevaCuenta) {
...
}
798 Capítulo 32: Código de autodocumentación

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.

Sorpresas de documentosSi encuentra algo que no es obvio en el código en sí, póngalo


en un comentario. Si ha utilizado una técnica engañosa en lugar de una sencilla para
mejorar el rendimiento, use los comentarios para señalar cuál sería la técnica sencilla y
cuantifique la ganancia de rendimiento lograda mediante el uso de la técnica engañosa.
Aquí hay un ejemplo:

Ejemplo en C++ de documentar una sorpresa


for ( elemento = 0; elemento < cuentaelementos; elemento++ ) {
// Usa el desplazamiento a la derecha para dividir por dos. Sustituir la //
operación de desplazamiento a la derecha reduce el tiempo de bucle en un 75
%. listaelementos[ elemento ] = listaelementos[ elemento ] >> 1;
}

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.

Si es de conocimiento común, ¿por qué documentarlo? Porque el propósito de la operación no es


realizar un desplazamiento a la derecha; es para realizar una división por dos. El hecho de que el
código no utilice la técnica más adecuada a su propósito es significativo. Además, la mayoría de los
compiladores optimizan la división de enteros por dos para que sea un desplazamiento a la derecha
de todos modos, lo que significa que la claridad reducida suele ser innecesaria. En este caso
particular, el compilador evidentemente no optimiza la división por dos, y el tiempo ahorrado será
significativo. Con la documentación, un programador que lea el código vería la motivación para usar
la técnica no obvia. Sin el comentario, el mismo programador se inclinaría a quejarse de que el
código es innecesariamente "inteligente" sin ninguna ganancia significativa en el rendimiento. Por lo
general, tales quejas están justificadas, por lo que es importante documentar las excepciones.
32.5 Técnicas de comentarios 799

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:

Ejemplo de C++ de diferenciación entre comentarios principales y secundarios


con subrayados: no recomendado
El comentario principal // copiar la porción de cadena de la tabla, omitiendo en el camino // cadenas que se van
está subrayado. a eliminar
//------------------------------------------------ -------------------------- // determina el número de cadenas en la tabla
Un comentario menor que es

parte de la acción descrita por el ...


comentario principal no está

subrayado aquí... // marca las cadenas a borrar. . .

. . . 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.

Un mejor enfoque es usar puntos suspensivos delante de los comentarios menores:

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

de la acción descrita por el

comentario principal está precedido


// ... marcar las cadenas que se eliminarán. . .
por puntos suspensivos aquí...

. . . 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.

Esta discusión de comentarios mayores y menores no se aplica al código sangrado dentro de


bucles y condicionales. En tales casos, a menudo tendrá un comentario amplio en la parte
superior del ciclo y comentarios más detallados sobre las operaciones dentro del código
sangrado. En esos casos, la sangría proporciona la clave de la organización lógica de los
comentarios. Esta discusión se aplica solo a párrafos secuenciales de código en los que varios
párrafos forman una operación completa y algunos párrafos están subordinados a otros.

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.

Suponga que encuentra que la rutina de la bibliotecaWriteData(datos, numItems, blockSize)funciona


correctamente excepto cuandotamaño de bloquees igual500. Funciona bien para499,501, y cualquier otro
valor que haya probado, pero descubrió que la rutina tiene un defecto que aparece solo cuandotamaño de
bloquees igual500. En el código que usaEscribir datos (), documente por qué está haciendo un caso especial
cuandotamaño de bloquees500. Aquí hay un ejemplo de cómo podría verse:

Ejemplo de Java de documentación de la solución alternativa para un error


blockSize = optimoBlockSize( numItems, sizePerItem );

/* El siguiente código es necesario para evitar un error en WriteData() que aparece


solo cuando el tercer parámetro es igual a 500. '500' se reemplazó con una
constante con nombre para mayor claridad.

*/
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!

No comente código engañoso; reescribirloAquí hay un comentario de un proyecto en el que trabajé:

C++ Ejemplo de comentario de código inteligente


// NOTA MUY IMPORTANTE:
// El constructor de esta clase toma una referencia a UiPublication.
CODIFICACIÓN // El objeto UiPublication NO DEBE SER DESTRUIDO antes que el objeto DatabasePublication //. Si es así, el
HORROR
objeto DatabasePublication hará que el programa // tenga una muerte horrible.

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.

Esta es una idea aterradora.

Comentar el código complicado es exactamente el enfoque equivocado. Los comentarios no pueden


rescatar código difícil. Como enfatizan Kernighan y Plauger, “No documente el código incorrecto,
reescríbalo” (1978).

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

Comentar declaraciones de datos


Referencia cruzadaPara obtener detalles Los comentarios para las declaraciones de variables describen aspectos de la variable que el nombre
sobre el formato de los datos, consulte
de la variable no puede describir. Es importante documentar los datos cuidadosamente; al menos
"Diseño de declaraciones de datos" en la

Sección 31.5. Para obtener detalles sobre


una empresa que estudió sus propias prácticas concluyó que las anotaciones en los datos son
cómo usar los datos de manera efectiva, incluso más importantes que las anotaciones en los procesos en los que se utilizan los datos (SDC, en
consulte los Capítulos 10
Glass 1982). Aquí hay algunas pautas para comentar datos:
hasta el 13.

Comentar las unidades de datos numéricosSi un número representa longitud, indique


si la longitud se expresa en pulgadas, pies, metros o kilómetros. Si es tiempo, indique si
está expresado en segundos transcurridos desde el 1-1-1980, milisegundos desde el
inicio del programa, etc. Si son coordenadas, indica si representan latitud, longitud y
altitud y si están en radianes o grados; ya sea que representen unX,Y,Zsistema de
coordenadas con origen en el centro de la tierra; y así. No asuma que las unidades son
obvias. Para un nuevo programador, no lo serán. Para alguien que ha estado trabajando
en otra parte del sistema, no lo serán. Después de que el programa haya sido
sustancialmente modificado, no lo será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

usar aserciones al principio y al


programación Ada era la capacidad de restringir los valores permitidos de una variable
final de una rutina para afirmar numérica a un rango de valores. Si su idioma no es compatible con esa capacidad (y la mayoría
que los valores de la variable
de los idiomas no lo son), use un comentario para documentar el rango de valores esperado.
deben estar dentro de un rango

prescrito. Para obtener más


Por ejemplo, si una variable representa una cantidad de dinero en dólares, indica que esperas
detalles, consulte la Sección 8.2, que esté entre $1 y $100. Si una variable indica un voltaje, indique que debe estar entre 105v y
"Afirmaciones". 125v.

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

Ejemplo de Visual Basic de declaraciones de variables muy bien documentadas


Atenuar cursorX como entero ' posición horizontal del cursor; oscila entre 1..MaxCols ' posición
Atenuar cursorY como entero vertical del cursor; va desde 1..MaxRows

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

Limitaciones de comentarios en los datos de entradaLos datos de entrada pueden provenir de un


parámetro de entrada, un archivo o una entrada directa del usuario. Las pautas anteriores se aplican tanto a
los parámetros de entrada de rutina como a otros tipos de datos. Asegúrese de que los valores esperados e
inesperados estén documentados. Los comentarios son una forma de documentar que se supone que una
rutina nunca debe recibir ciertos datos. Las aserciones son otra forma de documentar rangos válidos y, si las
usa, el código se vuelve mucho más autoverificador.

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

804 Capítulo 32: Código de autodocumentación

Comentar estructuras de control


Referencia cruzadaPara obtener El espacio antes de una estructura de control suele ser un lugar natural para poner un
más detalles sobre las estructuras de
comentario. si es unsio uncasodeclaración, puede proporcionar el motivo de la decisión y un
control, consulte la Sección 31.3,

"Estilos de diseño", la Sección 31.4,


resumen del resultado. Si es un bucle, puede indicar el propósito del bucle.
"Diseño de estructuras de control" y

los Capítulos 14 a 19.


Ejemplo en C++ de comentar el propósito de una estructura de control
Propósito del siguiente // copia el campo de entrada hasta la coma
ciclo. while ( ( *CadenaEntrada != ',' ) && ( *CadenaEntrada != FIN_DE_CADENA ) ) {
* campo = * cadena de entrada;
campo++;
cadena de entrada++;
Fin del bucle (útil para bucles } // while -- copia el campo de entrada
anidados más largos—
aunque la necesidad de tal * campo = END_OF_STRING;
comentario indica un código
demasiado complicado). if (*cadena de entrada! = FIN_DE_CADENA) {
// lee la coma anterior y los espacios en blanco subsiguientes para llegar al siguiente campo de entrada
Propósito del bucle. La posición del inputString++;
comentario deja en claro quecadena while ( ( *CadenaEntrada == ' ' ) && ( *CadenaEntrada != FIN_DE_CADENA ) ) {
de entradase está configurando para cadena de entrada++;
el bucle. }
} // si -- al final de la cadena

Este ejemplo sugiere algunas pautas:

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,

} // for clientIndex — registro de proceso para cada cliente

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:

Ejemplo de Java del uso de comentarios para mostrar el anidamiento

for ( índicetabla = 0; índicetabla < cuentatabla; índicetabla++ ) {


while (ÍndiceRegistro <CuentaRegistro) {
if (! Número de registro ilegal (índice de registro)) {
...
Estos comentarios indican qué } // si
estructura de control está } // tiempo
finalizando. } // por

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

cómo crear rutinas de alta calidad,


de cada rutina, independientemente de su tamaño o complejidad:
consulte el Capítulo 7.

Ejemplo de Visual Basic de un prólogo monolítico de rutina de fregadero de cocina


'**************************************************** ******************** '
Nombre: CopyString
CODIFICACIÓN '
HORROR
' Objetivo: Esta rutina copia una cadena de la cadena de origen (origen) a
' la cadena de destino (objetivo).
'
'Algoritmo: Obtiene la longitud de "origen" y luego copia cada carácter, uno a la
' vez, en "objetivo". Utiliza el índice de bucle como un índice de matriz
' tanto en "origen" como en "destino" e incrementa el índice de bucle/
' matriz después de copiar cada carácter.
'
'
' Entradas: aporte La cadena a copiar
'
' Salidas: producción La cadena para recibir la copia de "input"
'
' Supuestos de interfaz: Ninguno '

'Historial de modificaciones: Ninguno'

' Autor: Dwight K. Codificador

' Fecha de creación: 1/10/04 '


Teléfono: (555) 222-2255
' NSS: 111-22-3333
' Color de los ojos: Verde
' Doncella Nombre: Ninguna

' Sangre Escribe: AB-


' Apellido de soltera de la madre:
ninguno ' Auto favorito: Pontiac Aztek
' Matrícula personalizada: "Tek-ie" '***************************************
*******************************
806 Capítulo 32: Código de autodocumentación

Esto es ridículo.CopiarCadenaes presumiblemente una rutina trivial, probablemente menos de


cinco líneas de código. El comentario está totalmente fuera de proporción con la escala de la
rutina. Las partes sobre la rutinaObjetivoyAlgoritmoestán tensos porque es difícil describir algo
tan simple comoCopiarCadenaa un nivel de detalle que está entre "copiar una cadena" y el
código en sí. Los comentarios repetitivosSupuestos de interfazyHistorial de modificaciones
tampoco son útiles, simplemente ocupan espacio en la lista. Requerir el nombre del autor es
redundante con información que se puede recuperar con mayor precisión del sistema de
control de revisión. Requerir todos estos ingredientes para cada rutina es una receta para
comentarios inexactos y fallas en el mantenimiento. Es un montón de trabajo que nunca vale la
pena.

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.

Aquí hay algunas pautas para comentar las rutinas:

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

detalles sobre cómo crearlos, consulte la


hacer. La dificultad para crear una breve descripción es una señal de que el diseño no es tan bueno como
Sección 7.3, "Nombres correctos de debería ser. Vuelva al tablero de dibujo de diseño y vuelva a intentarlo. La breve declaración de resumen
rutinas".
debe estar presente en prácticamente todas las rutinas, excepto en las simples.ObteneryEstablecerrutinas
accesorias.

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

Ejemplo de Java de documentación de datos de entrada y salida donde se


declaran: buena práctica
public void InsertionSort(
int[] dataToSort, // elementos a ordenar en ubicaciones firstElement..lastElement int firstElement, //
índice del primer elemento a ordenar (>=0)
int lastElement // índice del último elemento a ordenar (<= MAX_ELEMENTS)
)

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

finales y sus problemas",


comentar también es una buena ilustración del valor de usar la sangría estándar en lugar de la
anteriormente en esta sección. sangría de línea final para las listas de parámetros de rutina; no tendría espacio para comentarios
significativos de línea final si usara la sangría de línea final. Los comentarios en el ejemplo tienen
poco espacio incluso con sangría estándar. Este ejemplo también demuestra que los comentarios no
son la única forma de documentación. Si los nombres de sus variables son lo suficientemente
buenos, es posible que pueda omitir comentarlos. Finalmente, la necesidad de documentar las
variables de entrada y salida es una buena razón para evitar los datos globales. ¿Dónde lo
documentas? Presumiblemente, documentas los globales en el prólogo del monstruo. Eso genera
más trabajo y, desafortunadamente, en la práctica generalmente significa que los datos globales no
se documentan. Eso es una lástima porque los datos globales deben documentarse al menos tanto
como cualquier otra cosa.

Aproveche las utilidades de documentación de código como JavadocSi el código del


ejemplo anterior se escribiera realmente en Java, tendría la capacidad adicional de configurar
el código para aprovechar la utilidad de extracción de documentos de Java, Javadoc. En ese
caso, "documentar parámetros donde se declaran" cambiaría para verse así:

Ejemplo de Java de documentación de datos de entrada y salida para aprovechar Javadoc


/**
* . . . <descripción de la rutina> ...
*
* @param dataToSort elementos para ordenar en ubicaciones firstElement..lastElement
* @param firstElement índice del primer elemento a ordenar (>=0)
* @param lastElement índice del último elemento a ordenar (<= MAX_ELEMENTS)
*/
public void InsertionSort(
int[] datosParaOrdenar,
primer elemento int,
último elemento int
)

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

orden" en la Sección 7.5. Para obtener

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

la Sección 7.5, "Cómo utilizar los


alguna suposición sobre el estado de las variables que recibe (valores legales e ilegales,
parámetros de rutina". A arreglos en orden, datos de miembros que se inicializan o contienen solo datos buenos, etc.),
suposiciones de documentos
documéntelos en el prólogo de rutina o donde los datos se declara. Esta documentación debe
usando aserciones, consulte

“Usar aserciones para


estar presente en prácticamente todas las rutinas.
documentar y verificar

condiciones previas y posteriores”


Asegúrese de que los datos globales que se utilizan estén documentados. Una variable global
en la Sección 8.2. es tanto una interfaz para una rutina como cualquier otra cosa y es tanto más peligrosa
porque a veces no lo parece.

Mientras escribe la rutina y se da cuenta de que está haciendo una suposición de interfaz,
anótela inmediatamente.

Comente las limitaciones de la rutina.Si la rutina proporciona un resultado numérico,


indique la precisión del resultado. Si los cálculos no están definidos bajo algunas condiciones,
documente las condiciones. Si la rutina tiene un comportamiento predeterminado cuando
tiene problemas, documente el comportamiento. Si se espera que la rutina funcione solo en
arreglos o tablas de cierto tamaño, indíquelo. Si sabe de modificaciones al programa que
romperían la rutina, documéntelos. Si se encontró con errores durante el desarrollo de la
rutina, documéntelos también.
32.5 Técnicas de comentarios 809

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.

Documentar la fuente de los algoritmos que se utilizan.Si ha utilizado un algoritmo de un libro o


una revista, documente el volumen y el número de página de donde lo tomó. Si usted mismo
desarrolló el algoritmo, indique dónde puede encontrar el lector las notas que ha realizado al
respecto.

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.

Comentar clases, archivos y programas


Referencia cruzadaPara obtener detalles sobre Las clases, los archivos y los programas se caracterizan por el hecho de que contienen múltiples
el diseño, consulte la Sección 31.8, "Disposición
rutinas. Un archivo o clase debe contener una colección de rutinas relacionadas. Un programa
de clases". Para obtener detalles sobre el uso de

clases, consulte el Capítulo 6, "Funcionamiento


contiene todas las rutinas de un programa. La tarea de documentación en cada caso es proporcionar
una vista significativa de alto nivel del contenido del archivo, clase o programa.
Clases".
810 Capítulo 32: Código de autodocumentación

Directrices generales para la documentación de la clase

Para cada clase, use un comentario de bloque para describir los atributos generales de la clase:

Describir el enfoque de diseño de la clase.Los comentarios de descripción general que brindan


información que no se puede revertir fácilmente a partir de los detalles de codificación son
especialmente útiles. Describa la filosofía de diseño de la clase, el enfoque de diseño general, las
alternativas de diseño que se consideraron y descartaron, etc.

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).

No documente los detalles de implementación en la interfaz de claseUna regla cardinal de la


encapsulación es que usted expone la información solo cuando es necesario saberla: si hay alguna duda
sobre si la información debe ser expuesta, el valor predeterminado es mantenerla oculta. En consecuencia,
los archivos de interfaz de clase deben contener la información necesaria para usar la clase, pero no la
información necesaria para implementar o mantener el funcionamiento interno de la clase.

Pautas generales para la documentación del archivo

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

// $Id: ClassName.java,v 1.1 2004/02/05 00:36:43 ismene Exp $

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.

Ejemplo de Java de una declaración de derechos de autor

// (c) Copyright 1993-2004 Steven C. McConnell. Reservados todos los derechos. . . .

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

El paradigma del libro para la documentación del programa

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.

Descubrieron que al pensar en el código como un tipo especial de libro y formatearlo en


consecuencia, podían lograr sus objetivos. En Book Paradigm, el código y su documentación están
organizados en varios componentes similares a los componentes de un libro para ayudar a los
programadores a obtener una vista de alto nivel del programa.

El “prefacio” es un grupo de comentarios introductorios como los que normalmente se encuentran al


principio de un archivo. Funciona como el prefacio de un libro. Le da al programador una visión
general del programa.

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

comprensión. Un estudio con programadores de la Universidad de Toronto produjo


resultados similares (Baecker y Marcus 1990).

The Book Paradigm enfatiza la importancia de proporcionar documentación que explique


tanto la organización de alto nivel como la de bajo nivel de su programa.

32.6 Normas IEEE


Para la documentación más allá del nivel del código fuente, las valiosas fuentes de información son
los Estándares de ingeniería de software del IEEE (Instituto para ingenieros eléctricos y eléctricos).
Los estándares IEEE son desarrollados por grupos compuestos por profesionales y académicos que
son expertos en un área en particular. Cada estándar contiene un resumen del área cubierta por el
estándar y generalmente contiene el esquema de la documentación apropiada para trabajar en esa
área.

Varias organizaciones nacionales e internacionales participan en el trabajo de normalización. El IEEE


es un grupo que ha tomado la delantera en la definición de estándares de ingeniería de software.
Algunos estándares son adoptados conjuntamente por ISO (Organización Internacional de
Estándares), EIA (Alianza de Industrias Electrónicas) o IEC (Consorcio Internacional de Ingeniería).

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.

Estándares de desarrollo de software


cc2e.com/3273 Aquí hay estándares de desarrollo de software a considerar:

IEEE Std 830-1998, práctica recomendada para especificaciones de requisitos de software

IEEE Std 1233-1998, Guía para desarrollar especificaciones de requisitos del sistema

IEEE Std 1016-1998, práctica recomendada para descripciones de diseño de software

IEEE Std 828-1998, Estándar para Planes de Gestión de Configuración de Software

IEEE Std 1063-2001, estándar para documentación de usuario de software

IEEE Std 1219-1998, estándar para mantenimiento de software


814 Capítulo 32: Código de autodocumentación

Estándares de aseguramiento de la calidad del software

cc2e.com/3280 Y aquí están los estándares de garantía de calidad del software:

IEEE Std 730-2002, Estándar para Planes de Garantía de Calidad de Software

IEEE Std 1028-1997, estándar para revisiones de software

IEEE Std 1008-1987 (R1993), estándar para pruebas de unidades de software

IEEE Std 829-1998, estándar para documentación de prueba de software

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 1058-1998, Estándar para Planes de Gestión de Proyectos de Software

IEEE Std 1074-1997, estándar para desarrollar procesos de ciclo de vida de software

IEEE Std 1045-1992, estándar para métricas de productividad de software

IEEE Std 1062-1998, Práctica recomendada para la adquisición 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

Descripción general de las normas

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

cc2e.com/3243 LISTA DE VERIFICACIÓN: Buena técnica para comentar


General
- ¿Puede alguien recoger el código e inmediatamente empezar a entenderlo?

- ¿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?

- ¿Se utiliza el proceso de programación de pseudocódigo para reducir el tiempo de

comentarios?

- ¿Se ha reescrito el código engañoso en lugar de comentarlo?

- ¿Están actualizados los comentarios?

- ¿Los comentarios son claros y correctos?

- ¿El estilo de comentarios permite que los comentarios se modifiquen fácilmente?

Declaraciones y Párrafos
- ¿El código evita los comentarios finales?

- ¿Los comentarios se centran enpor quémás bien quecómo?

- ¿Los comentarios preparan al lector para el código a seguir?

- ¿Todos los comentarios cuentan? ¿Se han eliminado o mejorado los comentarios
redundantes, superfluos y autoindulgentes?

- ¿Se documentan las sorpresas?

- ¿Se han evitado las abreviaturas?

- ¿Está clara la distinción entre comentarios mayores y menores?

- ¿Se comenta el código que soluciona un error o una función no documentada?

Declaraciones de datos

- ¿Se comentan las unidades en las declaraciones de datos?

- ¿Se comentan los rangos de valores de los datos numéricos?

- ¿Se comentan los significados codificados?

- ¿Se comentan las limitaciones en los datos de entrada?

- ¿Están las banderas documentadas a nivel de bit?

- ¿Se ha comentado cada variable global donde se declara?


- ¿Se ha identificado cada variable global como tal en cada uso, mediante una convención de
nomenclatura, un comentario o ambos?

- ¿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?

Archivos, clases y programas


- ¿Tiene el programa un documento breve, como el descrito en el Paradigma
del libro, que da una visión general de cómo está organizado el programa?

- ¿Se describe el propósito de cada archivo?

- ¿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.

- El código fuente debe contener la mayor parte de la información crítica sobre el


programa. Mientras el programa se esté ejecutando, es más probable que el código
fuente se mantenga actualizado que cualquier otro recurso, y es útil tener información
importante junto con el código.

- Un buen código es su propia mejor documentación. Si el código es lo suficientemente malo como


para requerir comentarios extensos, intente primero mejorar el código para que no necesite
comentarios extensos.

- 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

- 33.2 Inteligencia y humildad: página 821

- 33.3 Curiosidad: página 822

- 33.4 Honestidad Intelectual: página 826

- 33.5 Comunicación y Cooperación: página 828

- 33.6 Creatividad y Disciplina: página 829

- 33.7 Pereza: página 830

- 33.8 Características que no importan tanto como crees: página 830


- 33.9 Hábitos: página 833

Temas relacionados

- Temas en la artesanía del software: Capítulo 34

- Complejidad: Secciones 5.2 y 19.6

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.

Si es ingeniero de software, su material de construcción básico es el intelecto humano y su


herramienta principal estú. En lugar de diseñar una estructura hasta el último detalle y luego
entregar los planos a otra persona para que la construya, sabe que una vez que ha diseñado una
pieza de software hasta el último detalle, está lista. Todo el trabajo de programación es construir
castillos de aire, es una de las actividades más puramente mentales que puedes hacer.

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.

33.1 ¿No está el carácter personal fuera del tema?


La intensa interioridad de la programación hace que el carácter personal sea
especialmente importante. Ya sabes lo difícil que es dedicar ocho horas concentradas en
un día. Probablemente haya tenido la experiencia de agotarse un día por concentrarse
demasiado el día anterior o agotarse un mes por concentrarse demasiado el mes
anterior. Probablemente haya tenido días en los que trabajó bien desde las 8:00 a. m.
hasta las 2:00 p. m. y luego tuvo ganas de renunciar. Sin embargo, no renunciaste;
continuaste de 2:00 p. m. a 5:00 p. m. y luego pasaste el resto de la semana arreglando lo
que escribiste de 2:00 a 5:00 p. m.

El trabajo de programación es esencialmente no supervisable porque nadie sabe realmente en qué


estás trabajando. Todos hemos tenido proyectos en los que pasamos el 80 por ciento del tiempo
trabajando en una pequeña pieza que encontramos interesante y el 20 por ciento del tiempo
construyendo el otro 80 por ciento del programa.

Tu empleador no puede obligarte a ser un buen programador; muchas veces tu


empleador ni siquiera está en condiciones de juzgar si eres bueno. Si quieres ser grande,
eres responsable de hacerte grande. Es una cuestión de su carácter personal.

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

33.2 Inteligencia y Humildad


Nos convertimos en autoridades y La inteligencia no parece un aspecto del carácter personal, y no lo es. Coincidentemente, una
expertos en las esferas práctica y
gran inteligencia está vagamente relacionada con ser un buen programador.
científica por tantos actos

separados y horas de trabajo. Si


¿Qué? ¿No tienes que ser superinteligente?
una persona se mantiene

fielmente ocupada cada hora de la


No, no lo haces. Nadie es realmente lo suficientemente inteligente como para programar computadoras.
jornada laboral, puede contar con

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

Como se menciona en el Capítulo 5 (“Diseño en la construcción”), en la Conferencia del Premio


Turing de 1972, Edsger Dijkstra presentó un artículo titulado “El humilde programador”.
Argumentó que la mayor parte de la programación es un intento de compensar el tamaño
estrictamente limitado de nuestros cráneos. Las personas que mejor programan son las que
se dan cuenta de lo pequeños que son sus cerebros. son humildes Las personas que son
peores programando son las que se niegan a aceptar el hecho de que sus cerebros no están a
la altura de la tarea. Sus egos les impiden ser grandes programadores. Cuanto más aprendas a
compensar tu pequeño cerebro, mejor programador serás. Cuanto más humilde seas, más
rápido mejorarás.

El propósito de muchas buenas prácticas de programación es reducir la carga de las celdas grises.
Aquí están algunos ejemplos:

- El objetivo de "descomponer" un sistema es hacerlo más fácil de entender. (Consulte "Niveles


de diseño" en la Sección 5.2 para obtener más detalles).

- La realización de revisiones, inspecciones y pruebas es una forma de compensar las


falibilidades humanas anticipadas. Estas técnicas de revisión se originaron como parte
de la "programación sin ego" (Weinberg 1998). Si nunca cometió errores, no necesitaría
revisar su software. Pero sabes que tu capacidad intelectual es limitada, así que la
aumentas con la de otra persona.

- Mantener rutinas cortas reduce la carga en su cerebro.

- 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.

- El uso de convenciones de todo tipo libera su cerebro de los aspectos relativamente


mundanos de la programación, que ofrecen poca retribución.

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

desarrollo de software, consulte la


desarrollo de software, estará en una mejor posición para comprender los cambios y mover
Sección 34.2, “Elija su proceso”. a su grupo en una buena dirección.

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

experimentación. Para obtener más


idioma, escriba un programa corto para ejercitar la característica y ver cómo funciona.
información, consulte ¡Prototipo! Observe cómo se ejecuta el programa en el depurador. Es mejor trabajar con un
"Experimentación" en la Sección 34.9.
programa corto para probar un concepto que escribir un programa más grande con una
función que no comprende del todo.
33.3 Curiosidad 823

¿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.

El campo de la ingeniería de software hace un uso extraordinariamente limitado de ejemplos


de éxitos y fracasos pasados. Si estuviera interesado en la arquitectura, estudiaría los dibujos
de Louis Sullivan, Frank Lloyd Wright e IM Pei. Probablemente visitarías algunos de sus
edificios. Si estuviera interesado en la ingeniería estructural, estudiaría el Puente de Brooklyn;
el puente de Tacoma Narrows; y una variedad de otras estructuras de hormigón, acero y
madera. Estudiarías ejemplos de éxitos y fracasos en tu campo.

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).

Un artículo en elComunicaciones de la ACMabogó por aprender de los estudios de casos de


problemas de programación (Linn y Clancy 1992). El hecho de que alguien tenga que discutir
824 Capítulo 33: Carácter personal

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.

¡Leer!La fobia a la documentación es rampante entre los programadores. La documentación de la


computadora tiende a estar mal escrita y mal organizada, pero a pesar de todos sus problemas, hay
mucho que ganar si se supera el miedo excesivo a los fotones de la pantalla de la computadora oa
los productos de papel. La documentación contiene las llaves del castillo, y vale la pena dedicar
tiempo a leerla. Pasar por alto la información que está fácilmente disponible es un descuido tan
común que un acrónimo familiar en los grupos de noticias y tableros de anuncios es "¡RTFM!" que
significa "¡Lea el !#*%*@ Manual!"

Un producto de lenguaje moderno generalmente se incluye con un conjunto enorme de


código de biblioteca. El tiempo dedicado a navegar por la documentación de la biblioteca está
bien invertido. A menudo, la empresa que proporciona el producto lingüístico ya ha creado
muchas de las clases que necesita. Si es así, asegúrese de conocerlos. Lea la documentación
cada dos meses.

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,

“Plan de lectura de un desarrollador


libro es más de lo que la mayoría de los programadores leen cada año (DeMarco y Lister 1999). Un
de software”. poco de lectura contribuye en gran medida al avance profesional. Si lee un buen libro de
programación cada dos meses, aproximadamente 35 páginas a la semana, pronto tendrá una
comprensión firme de la industria y se distinguirá de casi todos los que lo rodean.

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.

- Nivel 2: IntroductorioUn programador intermedio que ha superado la fase de


principiante es capaz de usar las capacidades básicas de varios idiomas y se siente
muy cómodo en al menos un idioma.

- Nivel 3: CompetenciaUn programador competente tiene experiencia en un idioma o en


un entorno o en ambos. Un programador de este nivel puede conocer todas las
complejidades de J2EE o tener laManual de referencia de C++ anotadomemorizado Los
programadores de este nivel son valiosos para sus empresas y muchos programadores
nunca superan este nivel.

- Nivel 4: LiderazgoUn líder tiene la experiencia de un programador de Nivel 3 y reconoce que


la programación es solo un 15 por ciento comunicándose con la computadora y un 85 por
ciento comunicándose con personas. Sólo el 30 por ciento del tiempo de un programador
promedio se dedica a trabajar solo (McCue 1978). Incluso se dedica menos tiempo a
comunicarse con la computadora. El gurú escribe código para una audiencia de personas en
lugar de máquinas. Los verdaderos programadores de nivel gurú escriben un código que es
muy claro y también lo documentan. No quieren desperdiciar sus valiosas celdas grises
reconstruyendo la lógica de una sección de código que podrían haber leído en un comentario
de una oración.

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.

No es pecado ser principiante o intermedio. No es pecado ser un programador competente en lugar


de un líder. El pecado está en cuánto tiempo permaneces como principiante o intermedio después de
saber lo que tienes que hacer para mejorar.
826 Capítulo 33: Carácter personal

33.4 Honestidad Intelectual


Parte de madurar como programador profesional es desarrollar un sentido inflexible de honestidad
intelectual. La honestidad intelectual comúnmente se manifiesta de varias maneras:

- Negarse a fingir que es un experto cuando no lo es

- Admitir fácilmente sus errores

- Intentar comprender una advertencia del compilador en lugar de suprimir el mensaje

- Comprender claramente su programa, no compilarlo para ver si funciona

- Proporcionar informes de estado realistas

- Brindar estimaciones de cronograma realistas y mantenerse firme cuando la


gerencia le pida que las ajuste

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.

Un tipo relacionado de descuido intelectual ocurre cuando no entiendes bien tu programa y


“simplemente lo compilas para ver si funciona”. Un ejemplo es ejecutar el programa para ver si
debe usar<o<=. En esa situación, realmente no importa si el programa funciona porque no lo
entiendes lo suficientemente bien como para saber por qué funciona. Recuerde que las
pruebas solo pueden mostrar la presencia de errores, no su ausencia. Si no entiende el
programa, no puede probarlo a fondo. Sentirse tentado a compilar un programa para “ver qué
pasa” es una señal de advertencia. Puede significar que necesita retroceder hasta el diseño o
que comenzó a codificar antes de estar seguro de saber lo que estaba haciendo. Asegúrese de
tener un fuerte control intelectual sobre el programa antes de cederlo al compilador.

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

cuatro meses y entregarlo en seis de lo que prometería y entregarlo en seis.


Perderá credibilidad si se compromete y ganará respeto si se mantiene firme en su
estimación.

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.

¡Respuesta incorrecta! La gerencia es responsable de los problemas generales de la gestión de una


empresa. Si cierta capacidad de software tiene un valor de $250 000 para una empresa y usted
estima que su desarrollo costará $750 000, la empresa no debería desarrollar el software. Es
responsabilidad de la gerencia hacer tales juicios. Cuando el orador abogó por mentir sobre el costo
del proyecto, diciéndole a la gerencia que costaría menos de lo que realmente costaría, abogó por
robar encubiertamente la autoridad de la gerencia. Si cree que un proyecto es interesante, abre
nuevos caminos importantes para la empresa o proporciona una formación valiosa, dígalo. La
gerencia también puede sopesar esos factores. Pero engañar a la gerencia para que tome una
decisión equivocada podría costarle literalmente a la empresa cientos de miles de dólares. Si te
cuesta tu trabajo, habrás obtenido lo que te mereces.

33.5 Comunicación y Cooperación


Los programadores verdaderamente excelentes aprenden a trabajar y jugar bien con los demás.
Escribir código legible es parte de ser un jugador de equipo. Es probable que la computadora lea su
programa con la misma frecuencia que otras personas, pero es mucho mejor para leer código pobre
que las personas. Como pauta de legibilidad, tenga en cuenta a la persona que tiene que modificar
su código. Programar es comunicarse primero con otro programador y luego comunicarse con la
computadora.
33.6 Creatividad y Disciplina 829

33.6 Creatividad y Disciplina


Cuando salí de la escuela, pensé que era el mejor programador del mundo. Podría
escribir un programa imbatible de tres en raya, usar cinco lenguajes informáticos
diferentes y crear programas de 1000 líneas que FUNCIONARON (¡de verdad!). Luego
salí al mundo real. Mi primera tarea en el mundo real fue leer y comprender un
programa Fortran de 200 000 líneas y luego acelerarlo en un factor de dos. Cualquier
programador real le dirá que toda la codificación estructurada del mundo no lo
ayudará a resolver un problema como ese: se necesita talento real.
— Publicación de Ed

Es difícil explicarle a un recién graduado en ciencias de la computación por qué necesita


convenciones y disciplina de ingeniería. Cuando era estudiante universitario, el programa más
grande que escribí tenía unas 500 líneas de código ejecutable. Como profesional, he escrito docenas
de utilidades de menos de 500 líneas, pero el tamaño promedio del proyecto principal ha sido de
5000 a 25 000 líneas, y he participado en proyectos con más de medio millón de líneas de código.
Este tipo de esfuerzo no requiere las mismas habilidades a mayor escala, sino un nuevo conjunto de
habilidades en conjunto.

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.

En una retrospectiva de 15 años sobre el trabajo en el Laboratorio de Ingeniería de


Software de la NASA, McGarry y Pajerski informaron que los métodos y herramientas que
enfatizan la disciplina humana han sido especialmente efectivos (1990). Muchas personas
altamente creativas han sido extremadamente disciplinadas. “La forma es liberadora”,
como dice el refrán. Los grandes arquitectos trabajan dentro de las limitaciones de los
materiales físicos, el tiempo y el costo. Los grandes artistas también lo hacen. Cualquiera
que haya examinado los dibujos de Leonardo tiene que admirar su disciplinada atención
al detalle. Cuando Miguel Ángel diseñó el techo de la Capilla Sixtina, lo dividió en
colecciones simétricas de formas geométricas, como triángulos, círculos y cuadrados. Lo
diseñó en tres zonas correspondientes a tres etapas platónicas. Sin esta estructura y
disciplina autoimpuestas,

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

reducir el gasto total de energía. - Aplazar una tarea desagradable


Te hace escribir programas que

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.

33.8 Características que no importan tanto como crees

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,

consulte "Consejos para encontrar


sin progreso, digamos 15 minutos. Deje que su subconsciente mastique el problema por un tiempo.
defectos" en la Sección 23.2. Trate de pensar en un enfoque alternativo que evite el problema por completo. Vuelva a escribir la
sección problemática del código desde cero. Vuelve a él más tarde cuando tu mente esté fresca.
Luchar contra los problemas informáticos no es ninguna virtud. Evitarlos es mejor.

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.

El hecho de que la información cambie rápidamente en la programación genera una dinámica


extraña en el área de la "experiencia". En muchos campos, un profesional que tiene un historial de
logros puede relajarse, relajarse y disfrutar del respeto ganado por una serie de éxitos. En el
desarrollo de software, cualquiera que pase por alto rápidamente pierde el contacto. Para seguir
siendo valioso, tienes que estar al día. Para los programadores jóvenes y hambrientos, esto es una
ventaja. Los programadores mayores a veces sienten que ya se han ganado sus galones y les molesta
tener que demostrar su valía año tras año.

La conclusión sobre la experiencia es la siguiente: si trabaja durante 10 años, ¿obtiene 10 años de


experiencia o obtiene 1 año de experiencia 10 veces? Tienes que reflexionar sobre tus actividades
para obtener una verdadera experiencia. Si haces del aprendizaje un compromiso continuo,
obtendrás experiencia. Si no lo hace, no lo hará, no importa cuántos años tenga a sus espaldas.

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

clasificación" en la Sección 22.4.


declaraciones de asignación sean mucho más difíciles de encontrar que los errores en los índices de
los bucles (Gould 1975). Respondes a las críticas de manera amistosa o hostil. Siempre está buscando
formas de hacer que el código sea legible o rápido, o no lo está. Si tiene que elegir entre hacer que el
código sea rápido y hacerlo legible, y toma la misma decisión cada vez, en realidad no está eligiendo,
está respondiendo por costumbre.

Estudie la cita de Aristóteles y sustituya "virtudes de programación" por "virtudes morales".


Señala que no estás predispuesto a un buen o mal comportamiento, sino que estás constituido
de tal manera que puedes convertirte en un buen o mal programador. La forma principal en
que te vuelves bueno o malo en lo que haces es haciéndolo: los constructores construyen y los
programadores programan. Lo que haces se convierte en un hábito y, con el tiempo, tus
buenos y malos hábitos determinan si eres un buen o mal programador.

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

834 Capítulo 33: Carácter personal

¿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.

Weinberg, Gerald M.La psicología de la programación informática: edición del aniversario de


plata. New York, NY: Dorset House, 1998. Este libro clásico contiene una exposición detallada
de la idea de la programación sin ego y de muchos otros aspectos del lado humano de la
programación de computadoras. Contiene muchas anécdotas entretenidas y es uno de los
libros más legibles que se han escrito sobre desarrollo de software.

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.

McConnell, Steve.Desarrollo de software profesional.Boston, MA: Addison-Wesley, 2004. El capítulo 7,


“Preferidos por los huérfanos”, proporciona más detalles sobre las personalidades de los
programadores y el papel del carácter personal.

Puntos clave
- Su carácter personal afecta directamente su capacidad para escribir programas de computadora.

- Las características que más importan son la humildad, la curiosidad, la honestidad


intelectual, la creatividad y disciplina, y la pereza ilustrada.

- 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.

- Sorprendentemente, la inteligencia pura, la experiencia, la persistencia y las agallas duelen tanto


como ayudan.

- Muchos programadores no buscan activamente nueva información y técnicas y, en


cambio, confían en la exposición accidental en el trabajo a nueva información. Si dedica
un pequeño porcentaje de su tiempo a leer y aprender sobre programación, después de
unos meses o años se distinguirá dramáticamente de la corriente principal de la
programación.

- 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.1 Conquistar la complejidad: página 837

- 34.2 Elija su proceso: página 839

- 34.3 Escribir programas para personas primero, computadoras segundo: página 841

- 34.4 Programa en tu idioma, no en él: página 843


- 34.5 Centra tu atención con la ayuda de convenciones: página 844

- 34.6 Programa en términos del dominio del problema: página 845

- 34.7 Esté atento a la caída de rocas: página 848

- 34.8 Iterar, repetidamente, una y otra vez: página 850

- 34.9 Separarás el software y la religión: página 851

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.

34.1 Conquistar la complejidad


Referencia cruzadaPara obtener El impulso para reducir la complejidad está en el corazón del desarrollo de software, hasta tal
detalles sobre la importancia de la
punto que el Capítulo 5, "Diseño en la construcción", describió la gestión de la complejidad
actitud en la conquista de la

complejidad, consulte la Sección 33.2,


como el imperativo técnico principal del software. Aunque es tentador tratar de ser un héroe y
“Inteligencia y Humildad”. lidiar con problemas informáticos en todos los niveles, el cerebro de nadie es realmente capaz
de abarcar nueve órdenes de magnitud de detalle. Informática y software

837
838 Capítulo 34: Temas en la artesanía del software

La ingeniería ha desarrollado muchas herramientas intelectuales para manejar tal


complejidad, y las discusiones de otros temas en este libro han rozado varias de ellas:

- Dividir un sistema en subsistemas a nivel de arquitectura para que su cerebro pueda


concentrarse en una cantidad más pequeña del sistema a la vez.

- Definir cuidadosamente las interfaces de clase para que pueda ignorar el funcionamiento interno de
la clase.

- Preservar la abstracción representada por la interfaz de clase para que su cerebro


no tenga que recordar detalles arbitrarios.

- 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 jerarquías hereditarias profundas porque son intelectualmente exigentes.

- Evitar el anidamiento profundo de bucles y condicionales porque pueden ser reemplazados por
estructuras de control más simples que queman menos celdas grises.

- Evitandoirs porque introducen la no linealidad que se ha encontrado que es difícil de seguir


para la mayoría de las personas.

- Defina cuidadosamente su enfoque para el manejo de errores en lugar de utilizar una


proliferación arbitraria de diferentes técnicas de manejo de errores.

- Ser sistemático en el uso del mecanismo de excepción incorporado, que puede


convertirse en una estructura de control no lineal que es tan difícil de entender comoirs
si no se usa con disciplina.

- No permitir que las clases se conviertan en clases de monstruos que equivalgan a programas
completos en sí mismos.

- Mantener las rutinas cortas.

- 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.

- Usar convenciones para ahorrarle a su cerebro el desafío de recordar diferencias


arbitrarias y accidentales entre diferentes secciones de código.

- 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

interfaz de clase consistente, elimina la necesidad de preocuparse por los detalles de


implementación de la clase y simplifica su trabajo en general.

El objetivo de tener convenciones de codificación también es principalmente reducir la complejidad.


Cuando puede estandarizar decisiones sobre formato, bucles, nombres de variables, notaciones de
modelado, etc., libera recursos mentales que necesita para concentrarse en aspectos más
desafiantes del problema de programación. Una de las razones por las que las convenciones de
codificación son tan controvertidas es que las elecciones entre las opciones tienen una base estética
limitada pero son esencialmente arbitrarias. Las personas tienen las discusiones más acaloradas
sobre sus diferencias más pequeñas. Las convenciones son más útiles cuando te ahorran la molestia
de tomar y defender decisiones arbitrarias. Son menos valiosos cuando imponen restricciones en
áreas más significativas.

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.

En resumen, un objetivo principal del diseño y construcción de software es conquistar la


complejidad. La motivación detrás de muchas prácticas de programación es reducir la complejidad
de un programa, y reducir la complejidad podría decirse que es la clave más importante para ser un
programador efectivo.

34.2 Elija su proceso


Un segundo tema importante en este libro es la idea de que el proceso que utiliza para
desarrollar software es muy importante. En un proyecto pequeño, el talento del
programador individual es la mayor influencia en la calidad del software. Parte de lo que
hace que un programador individual tenga éxito es su elección de procesos.
840 Capítulo 34: Temas en la artesanía del software

En proyectos con más de un programador, las características organizacionales marcan una


diferencia mayor que las habilidades de los individuos involucrados. Incluso si tienes un gran
equipo, su habilidad colectiva no es simplemente la suma de las habilidades individuales de los
miembros del equipo. La forma en que las personas trabajan juntas determina si sus
habilidades se suman o se restan. El proceso que usa el equipo determina si el trabajo de una
persona apoya el trabajo del resto del equipo o lo socava.

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,

consulte la Sección 3.4,

"Requisitos Prerrequisito". Para


puede crear un diseño superior para ello. Si los requisitos y, posteriormente, el diseño
obtener detalles sobre las variaciones cambian mientras el software está en desarrollo, el código también debe cambiar, lo que
en los enfoques de desarrollo,
corre el riesgo de degradar la calidad del sistema.
consulte la Sección 3.2, “Determine el

tipo de software en el que está


"Claro", dices, "pero en el mundo real, nunca tienes requisitos estables, así que eso es una pista
trabajando”.
falsa". Nuevamente, el proceso que utiliza determina qué tan estables son sus requisitos y qué
tan estables deben ser. Si desea incorporar más flexibilidad a los requisitos, puede configurar
un enfoque de desarrollo incremental en el que planee entregar el software en varios
incrementos en lugar de todo a la vez. Esta es una atención al proceso, y es el proceso que usa
el que finalmente determina si su proyecto tiene éxito o falla. La Tabla 3-1 en la Sección 3.1 deja
en claro que los errores de requisitos son mucho más costosos que los errores de
construcción, por lo que centrarse en esa parte del proceso también afecta el costo y el
cronograma.

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

refinar sus propios métodos.


que se complete la base, será más difícil realizar cambios fundamentales en la arquitectura del
Aunque los programadores sistema. Las personas tendrán una inversión emocional en el diseño porque ya habrán escrito
siempre están luchando para
un código para él. Es difícil deshacerse de una mala base una vez que ha comenzado a
cumplir con algún plazo futuro o

pasado, la abstracción
construir una casa sobre ella.
metodológica es una sabia

inversión a largo plazo.


La razón principal por la que el proceso es importante es que en el software, la calidad debe incorporarse
—robert w floyd desde el primer paso en adelante. Esto va en contra de la sabiduría popular de que puedes codificar como el
infierno y luego probar todos los errores del software. Esa idea está completamente equivocada. Las
pruebas simplemente le informan las formas específicas en las que su software es defectuoso. Las pruebas
no harán que su programa sea más usable, más rápido, más pequeño, más legible o más extensible.

La optimización prematura es otro tipo de error de proceso. En un proceso eficaz, realiza


ajustes gruesos al principio y ajustes finos al final. Si fuera escultor, desbastaría la forma
general antes de comenzar a pulir las características individuales. La optimización
prematura desperdicia tiempo porque dedica tiempo a pulir secciones de código que no
necesitan ser pulidas. Puede pulir secciones que son lo suficientemente pequeñas y
rápidas tal como son, puede pulir el código que luego desecha y puede no desechar el
código incorrecto porque ya ha dedicado tiempo a pulirlo. Siempre piensa: “¿Estoy
haciendo esto en el orden correcto? ¿Cambiar el orden haría una diferencia?” Seguir
conscientemente un buen proceso.
34.3 Escribir programas para las personas primero, las computadoras después 841

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

vez", más adelante en este capítulo.


hacia abajo. También tiene la garantía de tener comentarios en el código sin tener que incluirlos más
tarde.

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

tu programanorte. Un laberinto de non sequiturs plagado de ingeniosos trucos y


comentarios irrelevantes. Compara MI PROGRAMA.

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.

A la computadora no le importa si su código es legible. Es mejor para leer instrucciones


binarias de máquina que para leer sentencias de lenguaje de alto nivel. Escribe código legible
porque ayuda a otras personas a leer su código. La legibilidad tiene un efecto positivo en todos
estos aspectos de un programa:

- comprensibilidad

- Revisabilidad

- Tasa de error

- depuración

- modificabilidad

- Tiempo de desarrollo: una consecuencia de todo lo anterior

- Calidad externa: una consecuencia de todo lo anterior


842 Capítulo 34: Temas en la artesanía del software

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

programador. Uno no pensaría


una razón suficiente para escribir código legible. Pero el código también se lee durante las
más en leer el programa de un revisiones. Se lee cuando usted u otra persona corrigen un error. Se lee cuando se modifica el
colega espontáneamente que en
código. Se lee cuando alguien intenta usar parte de tu código en un programa similar.
tomar una carta de amor y leerla.

Esto es esencialmente lo que era


Hacer que el código sea legible no es una parte opcional del proceso de desarrollo, y favorecer la
un programa, una carta de amor

del programador al hardware,


conveniencia del tiempo de escritura sobre la conveniencia del tiempo de lectura es una economía
llena de detalles íntimos que solo falsa. Debe hacer el esfuerzo de escribir un buen código, que puede hacer una vez, en lugar del
conocen los socios en una
esfuerzo de leer un código malo, que tendría que hacer una y otra vez.
aventura. En consecuencia, los

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

verbales tan populares entre los


Porque dentro de una o dos semanas estarás trabajando en otro programa y pensarás: “¡Oye! Ya
amantes que viven en la dichosa escribí esta clase la semana pasada. Simplemente dejaré caer mi antiguo código probado y
abstracción que supone que la
depurado y ahorraré algo de tiempo”. Si el código no es legible, ¡buena suerte!
suya es la única existencia en el

universo. Dichos programas son


La idea de escribir código ilegible porque eres la única persona que trabaja en un proyecto sienta un
ininteligibles para quienes están

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.

34.4 Programa en tu idioma, no en él


No limite su pensamiento de programación solo a los conceptos que son compatibles
automáticamente con su lenguaje. Los mejores programadores piensan en lo que quieren
hacer y luego evalúan cómo lograr sus objetivos con las herramientas de programación a su
disposición.

¿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.

¿Tu lenguaje no admite afirmaciones? Escribe lo tuyoafirmar()rutina. Es posible que no funcione


exactamente igual que un integradoafirmar(), pero todavía puedes darte cuenta de la mayoría de
afirmar()Beneficios de escribir su propia rutina. ¿Su idioma no admite tipos enumerados o
constantes con nombre? Está bien; puede definir sus propias enumeraciones y constantes con
nombre con un uso disciplinado de variables globales compatibles con convenciones de
nomenclatura claras.

En casos extremos, especialmente en entornos de nueva tecnología, sus herramientas pueden


ser tan primitivas que se ve obligado a cambiar significativamente el enfoque de programación
deseado. En tales casos, es posible que deba equilibrar su deseo de programar en el lenguaje
con las dificultades accidentales que se crean cuando el lenguaje hace que el enfoque deseado
sea demasiado engorroso. Pero en tales casos, se beneficiará aún más de las convenciones de
programación que lo ayudan a mantenerse alejado de las funciones más peligrosas de esos
entornos. En casos más típicos, la brecha entre lo que quiere hacer y lo que sus herramientas
admitirán fácilmente requerirá que haga solo concesiones relativamente menores a su
entorno.
844 Capítulo 34: Temas en la artesanía del software

34.5 Enfoca tu atención con la ayuda de convenciones


Referencia cruzadaPor un Un conjunto de convenciones es una de las herramientas intelectuales utilizadas para gestionar la complejidad. Los
análisis del valor de las
capítulos anteriores hablan de convenciones específicas. Esta sección presenta los beneficios de las convenciones
convenciones que se aplican al

diseño del programa, consulte


con muchos ejemplos.
"¿Cuánto es un buen diseño?"

¿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.

34.6 Programa en términos del dominio del problema


Otrométodo específico de tratar con la complejidad es trabajar en el nivel más alto
posible de abstracción. Una forma de trabajar a un alto nivel de abstracción es trabajar
en términos del problema de programación en lugar de la solución informática.

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
.

Separación de un programa en niveles de abstracción


Obviamente, tiene que trabajar en términos de nivel de implementación en algún nivel, pero puede aislar la
parte del programa que funciona en términos de nivel de implementación de la parte que funciona en
términos de dominio de problema. Si está diseñando un programa, considere los niveles de abstracción que
se muestran en la figura 34-1.
846 Capítulo 34: Temas en la artesanía del software

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.

Nivel 0: operaciones del sistema operativo e instrucciones de máquina

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.

Nivel 1: Estructuras y herramientas del lenguaje de programación

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.

Nivel 2: Estructuras de implementación de bajo nivel

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.

Nivel 3: Términos de dominio de problema de bajo nivel

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.

Nivel 4: Términos de dominio de problemas de alto nivel

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.

Técnicas de bajo nivel para trabajar en el dominio del problema


Incluso sin un enfoque arquitectónico completo para trabajar en el vocabulario del área del
problema, puede usar muchas de las técnicas de este libro para trabajar en términos del
problema del mundo real en lugar de la solución informática:

- 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.

- Use funciones booleanas para aclarar pruebas booleanas complejas.


848 Capítulo 34: Temas en la artesanía del software

34.7 Esté atento a las rocas que caen


La programación no es ni completamente un arte ni completamente una ciencia. Como se practica
típicamente, es un “artesanía” que está en algún lugar entre el arte y la ciencia. En el mejor de los casos, es
una disciplina de la ingeniería que surge de la fusión sinérgica del arte y la ciencia (McConnell 2004). Ya sea
arte, ciencia, artesanía o ingeniería, todavía se necesita mucho juicio individual para crear un producto de
software que funcione. Y parte de tener buen juicio en la programación de computadoras es ser sensible a
una amplia gama de señales de advertencia, indicaciones sutiles de problemas en su programa. Las señales
de advertencia en la programación lo alertan sobre la posibilidad de problemas, pero generalmente no son
tan evidentes como una señal de tráfico que dice "Cuidado con las rocas que caen".

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

código es engañoso, piense en reescribirlo para que no lo sea.

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.

Si te encuentras trabajando en código repetitivo o haciendo modificaciones similares en varias áreas,


deberías sentirte “inquieto e insatisfecho”, dudando que el control se haya centralizado
adecuadamente en clases o rutinas. Si le resulta difícil crear andamios para casos de prueba porque
no puede usar una clase individual con facilidad, debería sentir la "irritación de la duda" y
preguntarse si la clase está demasiado acoplada a otras clases. Si no puede reutilizar el código en
otros programas porque algunas clases son demasiado interdependientes, esa es otra señal de
advertencia de que las clases están acopladas demasiado estrechamente.

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.

34.8 Iterar, repetidamente, una y otra vez


La iteración es apropiada para muchas actividades de desarrollo de software. Durante la
especificación inicial de un sistema, trabaja con el usuario a través de varias versiones de requisitos
hasta que esté seguro de que está de acuerdo con ellos. Eso es un proceso iterativo. Cuando
incorpora flexibilidad a su proceso mediante la creación y entrega de un sistema en varios
incrementos, se trata de un proceso iterativo. Si utiliza la creación de prototipos para desarrollar
varias soluciones alternativas de forma rápida y económica antes de elaborar el producto final, esa es
otra forma de iteración. La iteración de los requisitos es quizás tan importante como cualquier otro
aspecto del proceso de desarrollo de software. Los proyectos fracasan porque se comprometen con
una solución antes de explorar alternativas. La iteración proporciona una manera de aprender sobre
un producto antes de construirlo.

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.

Las revisiones atraviesan la esencia del proceso de desarrollo, insertando iteraciones en


cualquier etapa en la que se llevan a cabo. El propósito de una revisión es verificar la calidad
del trabajo en un punto en particular. Si el producto no pasa la revisión, se devuelve para su
revisión. Si tiene éxito, no necesita más iteraciones.
34.9 Separarás el software y la religión 851

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.

34.9 Separarás el software y la religión


La religión aparece en el desarrollo de software en numerosas encarnaciones: como adhesión dogmática a
un único método de diseño, como creencia inquebrantable en un formato o estilo de comentario específico,
o como una evasión celosa de los datos globales. Cualquiera que sea el caso, siempre es inapropiado.

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,

consulte "Asuntos religiosos" en la Sección


profesionales puedan probar nuevos métodos prometedores. Los métodos tienen que ser
28.5. probados antes de que puedan ser completamente probados o refutados. La difusión de los
resultados de la investigación a los profesionales se denomina "transferencia de tecnología" y
es importante para avanzar en el estado de la práctica del desarrollo de software. Hay una
diferencia, sin embargo, entre difundir una nueva metodología y vender aceite de serpiente de
software. La idea de la transferencia de tecnología es mal atendida por los vendedores
ambulantes de metodologías dogmáticas que intentan convencerlo de que sus nuevos pasteles
de vaca de alta tecnología y de talla única resolverán todos sus problemas.

En lugar de aferrarse a la última moda milagrosa, utilice una combinación de métodos.


Experimente con los emocionantes métodos recientes, pero confíe en los antiguos y confiables.

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,

consulte la Sección 2.2, “Cómo usar


determinista, podría seguir una metodología rígida para su solución. Pero el desarrollo de software
metáforas de software”. Para obtener no es un proceso determinista; es heurístico, lo que significa que los procesos rígidos son
información sobre el eclecticismo en
inapropiados y tienen pocas esperanzas de éxito. En diseño, por ejemplo, a veces la descomposición
el diseño, consulte "Iterar" en la

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

Adherirse a un solo método también es perjudicial porque obliga a adaptar el problema a la


solución. Si decide el método de solución antes de comprender completamente el problema,
actúa prematuramente. Si restringe en exceso el conjunto de posibles soluciones, es posible
que descarte la solución más eficaz.

Inicialmente, se sentirá incómodo con cualquier metodología nueva, y el consejo de evitar la


religión en la programación no pretende sugerir que deba dejar de usar un nuevo método tan
pronto como tenga un pequeño problema para resolver un problema con él. Dale al nuevo
método un buen trato, pero también a los viejos métodos.

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

“Aplicación de técnicas de software: la


enfoques alternativos avanzados que no puede usar al mismo tiempo. Tienes que elegir uno u otro
caja de herramientas intelectual” en la para cada problema específico. Debe tratar las técnicas como herramientas en una caja de
Sección 2.3.
herramientas y usar su propio juicio para seleccionar la mejor herramienta para el trabajo. La
mayoría de las veces, la elección de la herramienta no importa mucho. Puede usar una llave de caja,
alicates de tornillo de banco o una llave inglesa. En algunos casos, sin embargo, la selección de la
herramienta es muy importante, por lo que siempre debe hacer su selección con cuidado. La
ingeniería es en parte una disciplina de hacer concesiones entre técnicas competidoras. No puede
hacer una compensación si ha limitado prematuramente sus opciones a una sola herramienta.

La metáfora de la caja de herramientas es útil porque concreta la idea abstracta del


eclecticismo. Supongamos que usted fuera un contratista general y su amigo Simple Simon
siempre usara pinzas de presión. Supongamos que se niega a usar una llave de caja o una llave
inglesa. Probablemente pensarías que era extraño porque no usaría todas las herramientas a
su disposición. Lo mismo es cierto en el desarrollo de software. En un nivel alto, tiene métodos
de diseño alternativos. En un nivel más detallado, puede elegir uno de varios tipos de datos
para representar cualquier diseño dado. En un nivel aún más detallado, puede elegir varios
esquemas diferentes para formatear y comentar el código, nombrar variables, definir
interfaces de clase y pasar parámetros de rutina.

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

aprende si un enfoque falla o tiene éxito: el experimento en sí es un éxito siempre


que resuelva el problema.

La experimentación es apropiada en tantos niveles como lo es el eclecticismo.


En cada nivel en el que esté listo para hacer una elección ecléctica,
probablemente pueda idear un experimento correspondiente para determinar
qué enfoque funciona mejor. En el nivel de diseño arquitectónico, un
experimento podría consistir en esbozar arquitecturas de software utilizando
tres enfoques de diseño diferentes. En el nivel de diseño detallado, un
experimento podría consistir en seguir las implicaciones de una arquitectura de
nivel superior utilizando tres enfoques de diseño de bajo nivel diferentes. En el
nivel del lenguaje de programación, un experimento podría consistir en escribir
un programa experimental corto para ejercitar la operación de una parte del
lenguaje con la que no está completamente familiarizado. El experimento puede
consistir en ajustar un fragmento de código y compararlo para verificar que sea
realmente más pequeño o más rápido.

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.

- El proceso de programación afecta significativamente al producto final.

- La programación en equipo es más un ejercicio de comunicación con personas que de


comunicación con una computadora. La programación individual es más un ejercicio de
comunicación contigo mismo que con un ordenador.

- 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.

- La programación en términos del problema en lugar de la solución ayuda a gestionar la


complejidad.

- Prestar atención a las señales de advertencia intelectuales como la "irritación de la duda" es


especialmente importante en la programación porque la programación es casi puramente una
actividad mental.

- Cuanto más itere en cada actividad de desarrollo, mejor será el producto de


esa actividad.

- 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

Dónde encontrar más


información
cc2e.com/3560 Contenido

- 35.1 Información sobre la construcción del software: página 856

- 35.2 Temas más allá de la construcción: página 857

- 35.3 Publicaciones periódicas: página 859

- 35.4 Plan de lectura de un desarrollador de software: página 860

- 35.5 Afiliación a una Organización Profesional: página 862

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

35.1 Información sobre la construcción del software


cc2e.com/3588 Originalmente escribí este libro porque no pude encontrar una discusión completa sobre la construcción de
software. En los años transcurridos desde que publiqué la primera edición, han aparecido varios buenos
libros.

Programador pragmático(Hunt y Thomas 2000) se centra en las actividades más estrechamente


asociadas con la codificación, incluidas las pruebas, la depuración, el uso de aserciones, etc. No
profundiza en el código en sí, pero contiene numerosos principios relacionados con la creación de un
buen código.

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

programación ágil, consultecc2e.com/


de los requisitos previos”), las afirmaciones del libro sobre la economía de la Programación Extrema no
3545. están respaldadas por la investigación de la industria, pero muchas de sus recomendaciones son útiles
durante la construcción, independientemente de si un equipo está usando Programación Extrema o algún
otro. Acercarse.

Un libro más especializado es el de Steve Maguire.Escritura de código sólido: técnicas de Microsoft


para desarrollar software C sin errores(Maguire 1993). Se centra en las prácticas de construcción de
aplicaciones de software de calidad comercial, en su mayoría basadas en las experiencias del autor
trabajando en las aplicaciones de Office de Microsoft. Se enfoca en técnicas aplicables en C. Es en
gran medida ajeno a los problemas de programación orientada a objetos, pero la mayoría de los
temas que aborda son relevantes en cualquier entorno.

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++.

cc2e.com/3549 Aunque está agotado y es difícil de encontrar,Programadores en el trabajo, de Susan Lammers


(1986), vale la pena la búsqueda. Contiene entrevistas con los programadores de alto perfil de
la industria. Las entrevistas exploran sus personalidades, hábitos de trabajo y filosofías de
programación. Las luminarias entrevistadas incluyen a Bill Gates (fundador de Microsoft), John
Warnock (fundador de Adobe), Andy Hertzfeld (desarrollador principal del sistema operativo
Macintosh), Butler Lampson (ingeniero senior en DEC, ahora en Microsoft), Wayne Ratliff
(inventor de dBase), Dan Bricklin (inventor de VisiCalc) y una docena más.
35.2 Temas más allá de la construcción 857

35.2 Temas más allá de la construcción


Más allá de los libros básicos descritos en la sección anterior, aquí hay algunos libros
que van más allá del tema de la construcción de software.

Resumen Material
cc2e.com/3595 Los siguientes libros brindan una descripción general del desarrollo de software desde una variedad de
puntos de vista:

de Robert L. GlassHechos y falacias de la ingeniería de software(2003) proporciona una introducción


legible a la sabiduría convencional de lo que se debe y no se debe hacer en el desarrollo de software.
El libro está bien investigado y proporciona numerosos indicadores de recursos adicionales.

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.

losSwebok: Guía para el cuerpo de conocimientos de ingeniería de software(Abran 2001) proporciona


una descomposición detallada del cuerpo de conocimiento de la ingeniería de software. Este libro se
ha sumergido en detalles en el área de construcción de software. La Guía del Swebok muestra
cuánto más conocimiento existe en el campo.

de Gerald WeinbergLa psicología de la programación informática(Weinberg 1998) está repleto


de fascinantes anécdotas sobre programación. Es de gran alcance porque fue escrito en un
momento en que todo lo relacionado con el software se consideraba acerca de la
programación. El consejo en la reseña original del libro en elEvaluaciones de informática ACM
es tan bueno hoy como lo era cuando se escribió la reseña:

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).

Si no puedes encontrarLa psicología de la programación informática, buscarEl


Hombre-Mes Mítico(Brooks 1995) ogenteware(DeMarco y Lister 1999). Ambos
llevan a casa el tema de que la programación es, ante todo, algo hecho por
personas y, en segundo lugar, algo que involucra computadoras.

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.

Descripción general de la ingeniería de software

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.

de Roger S. PressmanIngeniería de software: el enfoque de un profesional, 6ª ed. (Pressman


2004), es un tratamiento equilibrado de requisitos, diseño, validación de calidad y gestión. Sus
900 páginas prestan poca atención a las prácticas de programación, pero esa es una limitación
menor, especialmente si ya tienes un libro sobre construcción como el que estás leyendo.

La sexta edición de Ian Sommerville'sIngeniería de software(Sommerville 2000) es comparable al


libro de Pressman, y también proporciona una buena visión general de alto nivel del proceso de
desarrollo de software.

Otras bibliografías comentadas


cc2e.com/3502 Las buenas bibliografías informáticas son raras. Aquí hay algunos que justifican el esfuerzo que se necesita
para obtenerlos:

Evaluaciones de informática ACMes una publicación de interés especial de la Association for


Computing Machinery (ACM) que se dedica a reseñar libros sobre todos los aspectos de las
computadoras y la programación de computadoras. Las reseñas están organizadas de acuerdo con
un amplio esquema de clasificación, lo que facilita la búsqueda de libros en su área de interés. Para
obtener información sobre esta publicación y sobre la membresía en la ACM, consulte www.acm.org.

cc2e.com/3509 Escalera de desarrollo profesional de Construx Software (www.construx.com/escalera/). Este sitio


web proporciona programas de lectura recomendados para desarrolladores, evaluadores y
administradores de software.
35.3 Periódicos 859

35.3 Periódicos
Revistas de programadores Lowbrow
Estas revistas suelen estar disponibles en los quioscos locales:

cc2e.com/3516 Desarrollo de software.www.sdmagazine.com. Esta revista se centra en problemas de programación, menos


en consejos para entornos específicos que en los problemas generales a los que se enfrenta como
programador profesional. La calidad de los artículos es bastante buena. También incluye reseñas de
productos.

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.

Si no puede encontrar estas revistas en su quiosco local, muchos editores le enviarán


una edición gratuita y muchos artículos están disponibles en línea.

Revistas de programadores intelectuales

Normalmente no compras estas revistas en el quiosco. Por lo general, debe ir a la biblioteca


de una universidad importante o suscribirse a ellos para usted o su empresa:

cc2e.com/3530 Software IEEE.www.computer.org/software/. Esta revista bimensual se enfoca en la


construcción, administración, requisitos, diseño y otros temas de software de vanguardia. Su
misión es “construir la comunidad de profesionales de software líderes”. En 1993 escribí que es
“la revista más valiosa a la que se puede suscribir un programador”. Desde que escribí eso, he
sido editor en jefe de la revista, y todavía creo que es la mejor publicación periódica disponible
para un profesional de software serio.

cc2e.com/3537 Computadora IEEE.www.computer.org/computer/. Esta revista mensual es la publicación


principal de la Computer Society del IEEE (Instituto de Ingenieros Eléctricos y Electrónicos).
Publica artículos sobre un amplio espectro de temas informáticos y tiene estándares de
revisión escrupulosos para garantizar la calidad de los artículos que publica. Debido a su
amplitud, probablemente encontrará menos artículos que le interesen que enSoftware IEEE.

cc2e.com/3544 Comunicaciones de la ACM.www.acm.org/cacm/. Esta revista es una de las publicaciones


informáticas más antiguas y respetadas disponibles. Tiene la amplia carta de publicación sobre
la longitud y amplitud de la computación, un tema que es mucho más amplio de lo que era
incluso hace unos años. Al igual que conComputadora IEEE, debido a su amplitud,
probablemente encontrará que muchos de los artículos están fuera de su área de interés. La
revista tiende a tener un sabor académico, que tiene tanto un lado malo como un lado bueno.
El lado malo es que algunos de los autores escriben en un estilo académico confuso. El lado
bueno es que contiene información de vanguardia que no se filtrará a las revistas vulgares
durante años.
860 Capítulo 35: Dónde encontrar más información

Publicaciones de interés especial


Varias publicaciones brindan una cobertura detallada de temas especializados.

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/3558 La ACM también publica publicaciones de interés especial en inteligencia artificial,


computadoras e interacción humana, bases de datos, sistemas integrados, gráficos,
lenguajes de programación, software matemático, redes, ingeniería de software y otros
temas. Verwww.acm.orgpara más información.

Publicaciones del Mercado Popular

cc2e.com/3565 Todas estas revistas cubren lo que sus nombres sugieren que cubren.

Diario de usuarios de C/C++.www.cuj.com.

Diario del desarrollador de Java.www.sys-con.com/java/.

Programación de Sistemas Embebidos.www.embedded.com.

diario de linux.www.linuxjournal.com.

Revisión de Unix.www.unixreview.com.

Red de desarrolladores de Windows.www.wd-mag.com.

35.4 Plan de lectura de un desarrollador de software


cc2e.com/3507 Esta sección describe el programa de lectura con el que un desarrollador de software debe trabajar
para lograr una posición profesional completa en mi empresa, Construx Software. El plan descrito es
un plan básico genérico para un profesional de software que quiere centrarse en el desarrollo.
Nuestro programa de tutoría proporciona una mayor adaptación del plan genérico para apoyar los
intereses de un individuo, y dentro de Construx, esta lectura también se complementa con
capacitación y experiencias profesionales dirigidas.

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

Bentley, Jon.Perlas de programación, 2ª ed. Reading, MA: Addison-Wesley, 2000.

vidrio, roberto l.Hechos y falacias de la ingeniería de software. Boston, MA: Addison-


Wesley, 2003.

McConnell, Steve.Guía de supervivencia de proyectos de software. Redmond, WA: Microsoft Press, 1998.

McConnell, Steve.Código completo, 2ª ed. Redmond, WA: Microsoft Press, 2004.

Nivel de practicante
Para lograr el estado "intermedio" en Construx, un programador debe leer los siguientes
materiales adicionales:

Berczuk, Stephen P. y Brad Appleton.Patrones de gestión de configuración de software:


trabajo en equipo efectivo, integración práctica. Boston, MA: Addison-Wesley, 2003.

Fowler, Martín.UML destilado: una breve guía del objeto estándarModo CTLenguaje, 3d
ed. Boston, MA: Addison-Wesley, 2003.

vidrio, roberto l.Creatividad de software. Reading, MA: Addison-Wesley, 1995.

Kaner, Cem, Jack Falk, Hung Q. Nguyen.Pruebas de software informático, 2ª ed. Nueva York,
NY: John Wiley & Sons, 1999.

Larman, Craig.Aplicación de UML y patrones: una introducción al análisis y diseño orientado a


objetos y al proceso unificado, 2ª ed. Englewood Cliffs, Nueva Jersey: Prentice Hall, 2001.

McConnell, Steve.Desarrollo rápido. Redmond, WA: Microsoft Press, 1996.

Wiegers, Karl.Requisitos de Software, 2ª ed. Redmond, WA: Microsoft Press, 2003.

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.

Bass, Len, Paul Clements y Rick Kazman.Arquitectura de software en la práctica, 2ª ed.


Boston, MA: Addison-Wesley, 2003.

Fowler, Martín.Refactorización: mejora del diseño del código existente. Reading, MA: Addison-
Wesley, 1999.

Gama, Erich, et al.Patrones de diseño. Reading, MA: Addison-Wesley, 1995.


862 Capítulo 35: Dónde encontrar más información

Gil, Tom.Principios de Gestión de Ingeniería de Software. Wokingham, Inglaterra:


Addison-Wesley, 1988.

Maguire, Steve.Escribir código sólido. Redmond, WA: Microsoft Press, 1993.

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/.

35.5 Unirse a una organización profesional


cc2e.com/3535 Una de las mejores maneras de aprender más sobre programación es ponerse en contacto con otros
programadores que estén tan dedicados a la profesión como usted. Los grupos de usuarios locales
para hardware específico y productos lingüísticos son un tipo de grupo. Otros tipos son las
organizaciones profesionales nacionales e internacionales. La organización más orientada a los
profesionales es la IEEE Computer Society, que publica elComputadora IEEEy Software IEEErevistas
Para obtener información sobre la membresía, consultewww.computadora.org.

cc2e.com/3542 La organización profesional original fue la ACM, que publicaComunicaciones de la ACMy


muchas revistas de interés especial. Tiende a tener una orientación algo más académica que la
IEEE Computer Society. Para obtener información sobre la membresía, consultewww.acm.org.
Bibliografía
“Estándar de codificación de CA”. 1991.Revisión de Unix Aristóteles.La ética de Aristóteles: La Nicoma-
9, núm. 9 (septiembre): 42–43. ética limpia. Trans. por JAK Thomson.
Abdel-Hamid, Tarek K. 1989. “The Dynamic-
Rev. por Hugh Tredennick.
ics of Software Project Staffing: A System Harmondsworth, Middlesex, Inglaterra:
Dynamics Based Simulation Approach”. Penguin, 1976.
Transacciones IEEE sobre ingeniería de Armenise, Pascual. 1989. “Una estructura
softwareSE-15, núm. 2 (febrero): 109–19. Enfoque para la optimización del programa”.
Transacciones IEEE sobre ingeniería de
Abran, Alain, et al. 2001.Swebok: Guía de
softwareSE-15, núm. 2 (febrero): 101–8.
el cuerpo de conocimientos de ingeniería de
software: versión de prueba 1.00-mayo de Arnold, Ken, James Gosling y David
2001. Los Alamitos, CA: IEEE Computer Holmes. 2000.El lenguaje de
Society Press. programación Java, 3d ed. Boston, MA:
Abrash, Michael. 1992. “Flooring It: The Addison-Wesley.
Desafío de optimización”.Técnicas de PC Arturo, Lowell J. 1988.Evolución del software:
2, núm. 6 (febrero/marzo): 82–88. El desafío del mantenimiento de software.
Nueva York, NY: John Wiley & Sons.
Ackerman, A. Frank, Lynne S. Buchwald,
y Frank H. Lewski. 1989. "Inspecciones Agustín, NR 1979. “Leyes de Agustín
de software: un proceso de verificación y Programas Principales de Desarrollo de
efectivo".Software IEEE, mayo/junio de Sistemas.”Revisión de la gestión de sistemas
1989, 31–36. de defensa: 50–76.
Adams, James L. 2001.Bloque conceptual- Babich, W. 1986.Configuración de software
revienta: una guía para mejores ideas, 4ª ed. administración. Reading, MA: Addison-
Cambridge, MA: Perseus Publishing. Wesley.
Aho, Alfred V., Brian W. Kernighan y Bachman, Charles W. 1973. “The Program-
Peter J. Weinberg. 1977.El lenguaje de mer como navegante. Conferencia Premio
programación AWK. Reading, MA: Turing.Comunicaciones de la ACM16, núm. 11
Addison-Wesley. (noviembre): 653.

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

Barbour, Ian G. 1974.Mitos, modelos y Bastani, Farokh y Sitharama Iyengar.


Paradigmas: un estudio comparativo en 1987. "El efecto de las estructuras de datos
ciencia y religión. Nueva York, NY: Harper en la complejidad lógica de los
& Row. programas". Comunicaciones de la ACM30,
Barwell, Fred, et al. 2002.Profesional núm. 3 (marzo): 250–59.
VB.NET, 2ª ed. Birmingham, Reino Unido: Wrox. Bahías, Michael. 1999.Método de lanzamiento de software
odología. Englewood Cliffs, Nueva Jersey: Prentice
Basili, VR y BT Perricone. 1984. “Soft-
Hall.
Errores de software y complejidad: una
investigación empírica”.Comunicaciones de Beck, Kent. 2000.Programación extrema Ex-
la ACM27, núm. 1 (enero): 42–52. explicado: Abrazar el cambio. Reading, MA:
Addison-Wesley.
Basili, Victor R. y Albert J. Turner. 1975.
"Mejora iterativa: una técnica práctica para Beck, Kent. 2003.Desarrollo basado en pruebas:
el desarrollo de software". Transacciones Por ejemplo. Boston, MA: Addison-
IEEE sobre ingeniería de softwareSE-1, Wesley.
núm. 4 (diciembre): 390–96.
Beck, Kent. 1991. "Piensa como un objeto".
Basili, Victor R. y David M. Weiss. 1984. Revisión de Unix9, núm. 10 (octubre): 39–43.
"Una metodología para recopilar datos
Beck, Leland L. y Thomas E. Perkins.
válidos de ingeniería de software".
1983. "Una encuesta sobre la práctica de la
Transacciones IEEE sobre ingeniería de
ingeniería de software: herramientas, métodos y
softwareSE-10, núm. 6 (noviembre): 728–38.
resultados". Transacciones IEEE sobre ingeniería
Basili, Victor R. y Richard W. Selby. de softwareSE-9, núm. 5 (septiembre): 541–61.
1987. "Comparación de la efectividad de las
Beizer, Boris. 1990.Tecnología de prueba de software
estrategias de prueba de software".
niques, 2ª ed. Nueva York, NY: Van
Transacciones IEEE sobre ingeniería de software
Nostrand Reinhold.
SE-13, núm. 12 (diciembre): 1278-1296.
Bentley, Jon y Donald Knuth. 1986. “Lit-
Basili, Víctor R., et al. 2002. “Lecciones Erate Programación.”Comunicaciones de la
aprendido de 25 años de mejora de
ACM 29, no. 5 (mayo): 364–69.
procesos: El ascenso y la caída del
Laboratorio de Ingeniería de Software de Bentley, Jon, Donald Knuth y Doug
la NASA”,Actas de la 24ª Conferencia McIlroy. 1986. “Un programa de
Internacional sobre Ingeniería de Software alfabetización”. Comunicaciones de la ACM29,
, Orlando, Florida. núm. 5 (mayo): 471–83.

Basili, Victor R., Richard W. Selby y Dav- Bentley, Jon. mil novecientos ochenta y dos.Redacción Eficiente Pro-

identificación H. Hutchins. 1986. gramos. Englewood Cliffs, Nueva Jersey: Prentice

"Experimentación en ingeniería de software". Hall.

Transacciones IEEE sobre ingeniería de software Bentley, Jon. 1988.Más Programación


SE-12, núm. 7 (julio): 733–43. Perlas: Confesiones de un codificador. Reading,
Basili, Víctor, L. Briand y WL Melo. MA: Addison-Wesley.

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

Berczuk, Stephen P. y Brad Appleton. Boehm, Barry W. 1981.Ingeniería de software


2003.Patrones de gestión de configuración Ciencias económicas. Englewood Cliffs, Nueva Jersey:
de software: trabajo en equipo efectivo, Prentice Hall.
integración práctica. Boston, MA: Addison- Boehm, Barry W. 1984. “Ingeniero de software-
Wesley. ing economía.”Transacciones IEEE sobre
Berry, RE y BAE Meekings. 1985. “Un ingeniería de softwareSE-10, núm. 1
Análisis de estilo de programas en C”. (enero): 4–21.
Comunicaciones de la ACM28, núm. 1 Boehm, Barry W. 1987a. “Mejorando Soft-
(enero): 80–88. Productividad de los productos.”Computadora IEEE,
Bersoff, Edward H. 1984. “Elementos de septiembre, 43–57.
Gestión de configuración de software”. Boehm, Barry W. 1987b. “Industrial Soft-
Transacciones IEEE sobre ingeniería de ware Metrics Top 10 List”.Software IEEE
softwareSE-10, núm. 1 (enero): 79–87. 4, núm. 9 (septiembre): 84–85.
Bersoff, Edward H. y Alan M. Davis. Boehm, Barry W. 1988. “Un modelo espiral de
1991. "Impactos de los modelos de ciclo de
Desarrollo y mejora de software”.
vida en la gestión de configuración de
Computadora, mayo, 61–72.
software". Comunicaciones de la ACM34,
núm. 8 (agosto): 104–18. Boehm, Barry W. y Philip N. Papaccio.
1988. "Comprensión y control de los costos
Bersoff, Edward H., et al. 1980.Software del software".Transacciones IEEE sobre
Gestión de la configuración. Englewood Cliffs,
ingeniería de softwareSE-14, núm. 10
Nueva Jersey: Prentice Hall.
(octubre): 1462-1477.
Birrell, ND y MA Ould. 1985.una práctica Boehm, Barry W., ed. 1989.Tutorial: Suave-
manual técnico para el desarrollo de software
Gestión de riesgos de mercancías. Washington,
. Cambridge, Inglaterra: Cambridge
DC: IEEE Computer Society Press.
University Press.
Boehm, Barry W., et al. 1978.Características
Bloch, Joshua. 2001.Programa Java efectivo- de Calidad del Software. Nueva York, NY:
Guía del idioma ming. Boston, MA: Holanda Septentrional.
Addison-Wesley.
Boehm, Barry W., et al. 1984. “Un software
BLS 2002.Mano de perspectiva ocupacional- Entorno de desarrollo para mejorar la
libro Edición 2002-03, Oficina de estadísticas
productividad”.Computadora, junio, 30–
laborales.
44.
BLS 2004.Manual de perspectivas ocupacionales
Boehm, Barry W., TE Gray y T. See-
Edición 2004-05, Oficina de estadísticas laborales.
Waldt. 1984. "Creación de prototipos versus
Blum, Bruce I. 1989. “A Software Environ- especificación: un experimento multiproyecto".
ment: algunos resultados empíricos Transacciones IEEE sobre ingeniería de software
sorprendentes ".Actas del decimocuarto SE-10, núm. 3 (mayo): 290–303. También en Jones
taller anual de ingeniería de software, 29 1986b.
de noviembre de 1989. Greenbelt, MD: Boehm, Barry, et al. 2000a.Estimación del costo del software
Centro de Vuelo Espacial Goddard. timacion con Cocomo II. Boston, MA:
Documento SEL-89-007. Addison-Wesley.
Bodie, John. 1987.Modo Crujido. Nuevo Boehm, Barry. 2000b. “Software unificador
York, Nueva York: Yourdon Press.
Ingeniería e Ingeniería de Sistemas”,
Boehm, Barry y Richard Turner. 2004. Computadora IEEE, marzo de 2000, 114–116.
Equilibrar la agilidad y la disciplina: una
guía para perplejos. Boston, MA: Addison-
Wesley.
866 Bibliografía

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.

Fecha, Chris. 1977.Una introducción a los datos-


Dijkstra, Edsger. 1969. “Pro-
gramática.” Reimpreso en Yourdon 1979.
sistemas base. Reading, MA: Addison-
Wesley. Dijkstra, Edsger. 1972. “The Humble Pro-
Davidson, Jack W. y Anne M. Holler. gramática.”Comunicaciones de la ACM
15, núm. 10 (octubre): 859–66.
1992. "Subprogram Inlining: un estudio de sus
efectos en el tiempo de ejecución del programa". Dijkstra, Edsger. 1985. “Fruits of Misun-
Transacciones IEEE sobre ingeniería de software comprendiendo.”Datamación, 15 de febrero,
SE-18, núm. 2 (febrero): 89–102. 86–87.

Davis, PJ 1972. “Fidelity in Mathematical Dijkstra, Edsger. 1989. “Sobre la crueldad de


Discurso: ¿Uno y uno son realmente dos? Enseñando realmente informática”.
Mensual matemático estadounidense, marzo, Comunicaciones de la ACM32, núm. 12
252–63. (diciembre): 1397–1414.
DeGrace, Peter y Leslie Stahl. 1990. Dunn, Robert H. 1984.Reparación de defectos de software
Problemas perversos, soluciones correctas: un movimiento. Nueva York, NY: McGraw-Hill.
catálogo de paradigmas modernos de ingeniería
Ellis, Margaret A. y Bjarne Stroustrup.
de software. Englewood Cliffs, Nueva Jersey:
1990.El manual de referencia de C++
Yourdon Press.
anotado. Boston, MA: Addison-Wesley.
DeMarco, Tom y Timothy Lister. 1999. Elmasri, Ramez y Shamkant B. Navathe.
Peopleware: Proyectos y Equipos 1989.Fundamentos de los sistemas de bases de
Productivos, 2ª ed. Nueva York, NY: datos. Redwood City, CA: Benjamín/Cummings.
Dorset House.
Elshoff, James L. 1976. “An Analysis of
DeMarco, Tom y Timothy Lister. 1985. Algunos programas comerciales PL/I”.
"Desempeño del programador y los Transacciones IEEE sobre ingeniería de
efectos del lugar de trabajo".Actas de la 8ª softwareSE-2, núm. 2 (junio): 113–20.
Conferencia Internacional sobre Soft-
ingeniería de mercancías. Washington, DC: Elshoff, James L. 1977. “La influencia de
IEEE Computer Society Press, 268–72. Programación Estructurada en Perfiles de
Programa PL/I.”Transacciones IEEE sobre
De Marco, Tom. 1979.Análisis estructurado ingeniería de softwareSE-3, núm. 5
y Especificación de Sistemas: Herramientas y
(septiembre): 364–68.
Técnicas. Englewood Cliffs, Nueva Jersey:
Prentice Hall.

De Marco, Tom. mil novecientos ochenta y dos.software de control

Proyectos. Nueva York, Nueva York: Yourdon Press.


Bibliografía 869

Elshoff, James L. y Michael Marcotty. Fjelstad, RK y WT Hamlen. 1979.


1982. "Mejora de la legibilidad de los “Estudio de mantenimiento del programa de
programas informáticos para ayudar a la aplicaciones: informe a nuestros encuestados”.
modificación".Comunicaciones de la ACM25, Guía de Procedimientos 48, Filadelfia. Reimpreso
núm. 8 (agosto): 512–21. enTutorial sobre Mantenimiento de Software, G.
Parikh y N. Zvegintzov, eds. Los Alamitos, CA: CS
Endres, Alberto. 1975. “Análisis de errores
Press, 1983: 13–27.
y sus causas en los programas del sistema”.
Transacciones IEEE sobre ingeniería de Floyd, Roberto. 1979. “Los Paradigmas de
softwareSE-1, núm. 2 (junio): 140–49. Programación."Comunicaciones de la
Evangelista, Miguel. 1984. “Program Com- ACM22, núm. 8 (agosto): 455–60.
Complejidad y Estilo de Programación.” Fowler, Martín. 1999.Refactorización: mejora
Actas de la Primera Conferencia ing el diseño del código existente. Reading,
Internacional sobre Ingeniería de Datos. MA: Addison-Wesley.
Nueva York, NY: IEEE Computer Society
Fowler, Martín. 2002.Patrones de empresa
Press, 534–41.
Arquitectura de la aplicación. Boston, MA:
Fagan, Michael E. 1976. “Diseño y código Addison-Wesley.
Inspecciones para Reducir Errores en el
Fowler, Martín. 2003.UML destilado: un resumen
Desarrollo de Programas.”Diario de sistemas de
Guía del lenguaje de modelado de objetos
IBM15, núm. 3: 182–211.
estándar, 3d ed. Boston, MA: Addison-
Fagan, Michael E. 1986. “Advances in Soft- Wesley.
Inspecciones de mercancías.”Transacciones IEEE
Fowler, Martín. 2004.UML destilado,edición 3d.
sobre ingeniería de softwareSE-12, núm. 7 (julio):
Boston, MA: Addison-Wesley.
744–51.
Fowler, Priscilla J. 1986. “In-Process Inspec-
Soporte de administración de software federal
ciones de productos de trabajo en AT&T”.
Centro. 1986.Manual de banco de trabajo
Diario técnico de AT&T, marzo/abril, 102–12.
para programadores. Falls Church, VA: Oficina
de Desarrollo de Software y Tecnología de la Foxall, James. 2003.Estándares prácticos para
Información. Microsoft Visual Basic .NET. Redmond,
WA: Microsoft Press.
Feiman, J. y M. Driver. 2002. “Líder
Lenguajes de programación para la planificación de Freedman, Daniel P. y Gerald M. Wein-
carteras de TI”, informe de Gartner Research iceberg. 1990.Manual de Recorridos,
SPA-17-6636, 27 de septiembre de 2002. Inspecciones y Revisiones Técnicas, 3d ed.
Nueva York, NY: Dorset House.
Fetzer, James H. 1988. “Program Verifica-
ción: la idea misma”.Comunicaciones de la Freeman, Peter y Anthony I. Wasser-
ACM31, núm. 9 (septiembre): 1048– 63. hombre, eds. 1983.Tutorial sobre Técnicas
de Diseño de Software, 4ª ed. Silver Spring,
MD: IEEE Computer Society Press.
PUBLICACIÓN FIPS 38,Directrices para la documentación
de Programas Informáticos y Sistemas Gama, Erich, et al. 1995.Patrones de diseño.
Automatizados de Datos. 1976. Departamento de Reading, MA: Addison-Wesley.
Comercio de los Estados Unidos. Oficina Gerber, Ricardo. 2002.Optimización de software
Nacional de Normas. Washington, DC: Imprenta Libro de recetas: Recetas de alto rendimiento
del Gobierno de EE. UU., 15 de febrero. para la arquitectura Intel. Intel Press.
Pezman, Charles. 1996. “Escriben el Gibson, Elizabeth. 1990. “Objetos—Nacidos
Cosas correctas,"empresa rapida, diciembre de y Criado.”BYTE, octubre, 245–54.
1996.
870 Bibliografía

Gilb, Tom y Dorothy Graham. 1993. Gordon, Scott V. y James M. Bieman.


Inspección de software. Wokingham, 1991. "Prototipos rápidos y calidad del software:
Inglaterra: Addison-Wesley. lecciones de la industria".Novena Conferencia
Anual de Calidad de Software del Noroeste del
Gil, Tom. 1977.Métricas de software. Leva-
Pacífico, 7 y 8 de octubre. Centro de
puente, MA: Winthrop.
Convenciones de Oregón, Portland, Oregón.
Gil, Tom. 1988.Principios de ingeniería de software
Gestión de la inversión. Wokingham, Gorla, N., AC Benander y BA
Benandro. 1990. "Estimación del esfuerzo de
Inglaterra: Addison-Wesley.
depuración mediante métricas de software".
Gil, Tom. 2004.Ingeniería Competitiva. Transacciones IEEE sobre ingeniería de software
Boston, MA: Addison-Wesley. Descargable SE-16, núm. 2 (febrero): 223–31.
desdewww.result-planning.com.
Gould, John D. 1975. “Algunos
Ginac, Frank P. 1998.Orientado al cliente Evidencia sobre cómo las personas depuran
Aseguramiento de la calidad del software. Englewood programas de computadora”.Revista internacional
Cliffs, Nueva Jersey: Prentice Hall. de estudios hombre-máquina7:151–82.
Vidrio, Robert L. 1982.Programación moderna Grady, Robert B. 1987. “Medir y
Prácticas: un informe de la industria. Englewood Gestión del mantenimiento del software”.
Cliffs, Nueva Jersey: Prentice Hall. Software IEEE4, núm. 9 (septiembre): 34–45.
Vidrio, Robert L. 1988.Comunicación de software Grady, Robert B. 1993. “Reglas prácticas de
Habilidades de ción. Englewood Cliffs, Nueva Jersey: Prentice
Pulgar para administradores de software”.El
Hall.
practicante de software3, núm. 1 (enero/
Vidrio, Robert L. 1991.Conflicto de software: Es- febrero): 4–6.
dice sobre el arte y la ciencia de la ingeniería de Grady, Robert B. 1999. “An Economic Re-
software. Englewood Cliffs, Nueva Jersey: Yourdon modelo de decisión de arrendamiento:
Press. información sobre la gestión de proyectos de
Vidrio, Robert L. 1995.Creatividad de software. software”. En Actas de la Conferencia de
Reading, MA: Addison-Wesley. Aplicaciones de Medición de Software, 227–
239. Orange Park, FL: Ingeniería de calidad de
Glass, Robert L. 1999. “Inspecciones—Algunos
software.
Hallazgos sorprendentes”,Comunicaciones de
la ACM, abril de 1999, 17–19. Grady, Robert B. y Tom Van Slack. 1994.
“Lecciones clave para lograr un uso
Glass, Robert L. 1999. “The realities of soft-
generalizado de la inspección”,Software IEEE,
beneficios de la tecnología de artículos”,
julio de 1994.
Comunicaciones de la ACM, febrero de 1999, 74–79.
Grady, Robert B. 1992.Software Práctico
Vidrio, Robert L. 2003.Hechos y falacias de
Métricas para la gestión de proyectos y la mejora
Ingeniería de software. Boston, MA:
de procesos. Englewood Cliffs, Nueva Jersey:
Addison-Wesley.
Prentice Hall.
Glass, Robert L. y Ronald A. Noiseux.
Grady, Robert B. y Deborah L. Caswell.
1981.Guía de mantenimiento de software.
1987.Métricas de software: establecimiento de un
Englewood Cliffs, Nueva Jersey: Prentice Hall.
programa para toda la empresa. Englewood Cliffs,
Gordon, Ronald D. 1979. “Measuring Im- Nueva Jersey: Prentice Hall.
mejoras en la claridad del programa”.
Verde, Pablo. 1987. “Factores humanos en
Transacciones IEEE sobre ingeniería de
Sistemas informáticos, algunas lecturas
softwareSE-5, núm. 2 (marzo): 79–90.
útiles”.Boletín Sigchi19, núm. 2: 15–20.
Bibliografía 871

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

Haley, Thomas J. 1996. “Proceso de software


omento Ecosistemas. Boston, MA:
Mejora en Raytheon”.Software IEEE, Addison-Wesley.
noviembre de 1996. Hildebrand, JD 1989. “An Engineer's Ap-
acercarse.”Lenguaje de ordenador, del 5 al 7 de
Hansen, John C. y Roger Yim. 1987. “En-
octubre.
Estilos de dentación en C.”SIGSMALL/PC
Notas13, núm. 3 (agosto): 20–23. Hoare, Charles Anthony Richard, 1981. “La
Ropa vieja del emperador.Comunicaciones de
Hanson, Dines. 1984.En funcionamiento. Nuevo
la ACM, febrero de 1981, 75–83.
York, Nueva York: Yourdon Press.

Harrison, Warren y Curtis Cook. 1986. Hollocker, Charles P. 1990.Software Re-


manual de opiniones y auditorías. Nueva
"¿Son los condicionales profundamente anidados
York, NY: John Wiley & Sons.
menos legibles?"Revista de Sistemas y Software
6, núm. 4 (noviembre): 335–42. Houghton, Raymond C. 1990. “Una Oficina
Biblioteca para profesionales de ingeniería de
Hasan, Jeffrey y Kenneth Tu. 2003.Por-
software”.Ingeniería de Software: Herramientas,
Ajuste de rendimiento y optimización de
Técnicas, Práctica, mayo/junio, 35–38.
aplicaciones ASP.NET. Apres.
Hass, Anne Mette Jonassen. 2003.Configura-
Howard, Michael y David Le Blanc.
Principios y prácticas de gestión de 2003.Escribir código seguro,2d ed.
raciones, Boston, MA: Addison-Wesley. Redmond, WA: Microsoft Press.

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

IEEE Std 1540-2001, estándar para software 1994, 7–13.

Procesos del Ciclo de Vida - Gestión de Riesgos


Bibliografía 873

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”.

ambiente, Volumen B, TEX: El Programa. er diseño del sistema.”Software IEEE1, no.


Reading, MA: Addison-Wesley. 1 (enero): 11–28.
Knuth, Donald. 1997a.El arte de la computadora Larman, Craig y Rhett Guthrie. 2000.
Programación, vol. 1,Algoritmo Guía de lenguaje y rendimiento de Java 2.
fundamental- ritmos, 3d ed. Reading, MA: Englewood Cliffs, Nueva Jersey: Prentice Hall.
Addison-Wesley. Larman, Craig. 2001.Aplicando UML y
Knuth, Donald. 1997b.El arte de la computadora Patrones: una introducción al análisis y
Programación, vol. 2,Algo semimérico- diseño orientado a objetos y al proceso
ritmos, 3d ed. Reading, MA: Addison- unificado, 2ª ed. Englewood Cliffs, Nueva
Wesley. Jersey: Prentice Hall.

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

Lieberherr, Karl J. e Ian Holland. 1989. Mannino, P. 1987. “Presentación y


"Asegurar un buen estilo para programas orientados Comparación de cuatro metodologías de
a objetos".Software IEEE, septiembre de 1989, págs. desarrollo de sistemas de información”.Notas
38 y ss. de ingeniería de software12, núm. 2 (abril):
26–29.
Lientz, BP y EB Swanson. 1980.Suave-
Gestión de mantenimiento de mercancías. Manzo, Juan. 2002. “Odisea y otros
Reading, MA: Addison-Wesley. Historias de éxito de la ciencia del código”.Diafonía,
octubre de 2002.
Lind, Randy K. y K. Vairavan. 1989. “Un
Investigación experimental de métricas de Marca, David. 1981. "Algo de estilo Pascal
software y su relación con el esfuerzo de Pautas."Avisos de ACM Signplan16, núm. 4
desarrollo de software”.Transacciones IEEE (abril): 70–80.
sobre ingeniería de softwareSE-15, núm. 5
Marzo, Steve. 1999. “Learning from Path-
(mayo): 649–53.
Bumpy Start del buscador”.Pruebas de
Linger, Richard C., Harlan D. Mills y Ber- Software e Ingeniería de Calidad, septiembre/
Nardo I. Witt. 1979.Programación octubre de 1999, pp. 10f.
Estructurada: Teoría y Práctica. Reading, Marcotty, Michael. 1991.Implementación de software
MA: Addison-Wesley. mención. Nueva York, NY: Prentice Hall.
Linn, Marcia C. y Michael J. Clancy. Martín, Robert C. 2003.software ágil
1992. "El caso de los estudios de casos de
Desarrollo: principios, patrones y prácticas.
problemas de programación".Comunicaciones
Upper Saddle River, Nueva Jersey: Pearson
de la ACM35, núm. 3 (marzo): 121–32.
Education.
Liskov, Bárbara y Stephen Zilles. 1974. Macabe, Tom. 1976. “A Complexity Mea-
"Programación con tipos de datos abstractos".
Por supuesto."Transacciones IEEE sobre ingeniería
Avisos de ACM Signplan9, núm. 4: 50–59.
de software, SE-2, núm. 4 (diciembre): 308–20.
Liskov, Bárbara. “Abstracción de datos y Hi- McCarthy, Jim. 1995.Dinámica del Software
jerarquía,”ACM SIGPLAN Nohielos, mayo de
Desarrollo. Redmond, WA: Microsoft
1988.
Press.
Littman, David C., et al. 1986. “Mental McConnell, Steve. 1996.Desarrollo rápido
Modelos y Mantenimiento de Software.” En mento. Redmond, WA: Microsoft Press.
Soloway e Iyengar 1986: 80–98.
McConnell, Steve. 1997a. "El programa-
Longstreet, David H., ed. 1990.Software más escritura,”Software IEEE, julio/agosto
Mantenimiento y Computación. Los de 1997.
Alamitos, CA: IEEE Computer Society Press.
McConnell, Steve. 1997b. “Lograr Lean-
Loy, Patrick H. 1990. “A Comparison of Ob- er Software,”Software IEEE, noviembre/
Métodos de desarrollo estructurados y
diciembre de 1997.
orientados a objetos”.Notas de ingeniería de
software15, núm. 1 (enero): 44–48. McConnell, Steve. 1998a.Proyecto de Software
Guía de supervivencia. Redmond, WA: Microsoft
Mackinnon, Tim, Steve Freeman y Philip Press.
Craig. 2000. "Endo-Pruebas: Pruebas unitarias
con objetos simulados",Programación McConnell, Steve. 1998b. "Por qué tú
extremag y Procesos Flexibles Ingeniería de debería usar rutinas, rutinariamente”,
Software - Conferencia XP2000. Software IEEE, vol. 15, No. 4, julio/agosto
de 1998.
Maguire, Steve. 1993.Escribir código sólido.
Redmond, WA: Microsoft Press. McConnell, Steve. 1999. “Brooks Law Re-
pelado?Software IEEE, noviembre/
diciembre de 1999.
876 Bibliografía

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.

Meyers, Scott. 1998.Efectivo C++: 50 Especificación Moore, James W. 1997.Ingeniero de software-


Maneras de mejorar sus programas y Estándares de ingeniería: la hoja de ruta de un
diseños, 2ª ed. Reading, MA: Addison- usuario. Los Alamitos, CA: IEEE Computer Society
Wesley. Press.
Bibliografía 877

Morales, Alexandra Weber. 2003. “La Norman, Donald A. 1988.La psicología de


Entrenador consumado: Watts Humphrey, Cosas cotidianas. Nueva York, NY: Libros
padre de Cmm y autor de Winning with básicos. (También publicado en rústica como
Software, explica cómo mejorar en lo que El diseño de las cosas cotidianas. Nueva York,
haces.SD mostrar todos los días, 16 de NY: Doubleday, 1990.)
septiembre de 2003. Omán, Paul y Shari Lawrence Pfleeger,
Myers, Glenford J. 1976.Confiabilidad del software eds. 1996.Aplicación de métricas de software.
ty. Nueva York, NY: John Wiley & Sons. Los Alamitos, CA: IEEE Computer Society
Press.
Myers, Glenford J. 1978a.Compuesto/Estruc-
diseño natural. Nueva York, NY: Van Omán, Paul W. y Curtis R. Cook. 1990a.
Nostrand Reinhold. “El paradigma del libro para un mantenimiento
mejorado”.Software IEEE, enero, 39–45.
Myers, Glenford J. 1978b. “Un control
Experimente en Pruebas de Programas y Omán, Paul W. y Curtis R. Cook. 1990b.
Tutoriales/Inspecciones de Código.” "El estilo tipográfico es más que
Comunicaciones de la ACM21, núm. 9 cosmético".Comunicaciones de la ACM
(septiembre): 760–68. 33, núm. 5 (mayo): 506–20.
Myers, Glenford J. 1979.El arte del software Ostrand, Thomas J. y Elaine J. Weyuker.
Pruebas. Nueva York, NY: John Wiley & 1984. "Recopilación y categorización de datos
Sons. de errores de software en un entorno
industrial".Revista de Sistemas y Software
Myers, Ware. 1992. “Good Software Practice-
4, núm. 4 (noviembre): 289–300.
es paga, ¿o lo hacen ellos?”Software IEEE,
marzo, 96–97. Page-Jones, Meilir. 2000.Fundamentos de
Diseño Orientado a Objetos en UML. Boston,
Naisbitt, John. mil novecientos ochenta y dos.Megatendencias. Nuevo
MA: Addison-Wesley.
York, Nueva York: Warner Books.

laboratorio de ingeniería de software de la nasa,


Page-Jones, Meilir. 1988.La guía práctica
al Diseño de Sistemas Estructurados. Englewood
1994.Guía de medición de software, junio
Cliffs, Nueva Jersey: Yourdon Press.
de 1995, NASA-GB-001-94. Disponible de
http://sel.gsfc.nasa.gov/website/ Parikh, G. y N. Zvegintzov, eds. 1983.
documents/online-doc/94-102.pdf. Tutorial sobre Mantenimiento de Software.
Los Alamitos, CA: IEEE Computer Society
NCES 2002. Centro Nacional de Educación
Press.
Estadísticas,2001 Compendio de Estadísticas Educativas,Número de documento NCES
Parikh, Girish. 1986.manual de software 2002130, abril de 2002.
Mantenimiento. Nueva York, NY: John Wiley &
Sons.
Nevison, John M. 1978.El librito de BA-
Estilo SIC. Reading, MA: Addison-Wesley. Parnas, David L. 1972. “Sobre los criterios para
Utilícese para descomponer sistemas en
Recién llegado, Joseph M. 2000. “Optimización:
módulos”.Comunicaciones de la ACM5,
Tu peor enemigo”, mayo de 2000,
núm. 12 (diciembre): 1053–58.
www.platija.com/optimización.htm.
Norcio, AF 1982. “Sangría, Documentación Parnas, David L. 1976. “On the Design and
Desarrollo de Familias de Programas”.
tación y comprensión del programador.”
Actas: Factores humanos en sistemas Transacciones IEEE sobre ingeniería de
informáticos, 15 al 17 de marzo de 1982, softwareSE-2, 1 (marzo): 1–9.
Gaithersburg, MD:118–20. Parnas, David L. 1979. “Diseño de Software
para la Facilidad de Extensión y Contracción.”
Transacciones IEEE sobre ingeniería de
softwareSE-5, núm. 2 (marzo): 128–38.
878 Bibliografía

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

Putnam, Lawrence H. y Ware Myers. Rogers, Everett M. 1995.Difusión de Innova-


1997.Software de fuerza industrial: ciones, 4ª ed. Nueva York, NY: Prensa
gestión eficaz mediante la medición. libre.
Washington, DC: IEEE Computer Rombach, H. Dieter. 1990. “Design Mea-
Society Press. seguros: algunas lecciones aprendidas”.
Putnam, Lawrence H. y Ware Myers. Software IEEE, 17–25 de marzo.
2000. “Lo que hemos aprendido”. Descargable
Rubin, Frank. 1987. “'GOTO Considerado
desdewww.qsm.com, junio de 2000.
Dañino' Considerado Dañino.” Carta al
Raghavan, Sridhar A. y Donald R. Chand. editor.Comunicaciones de la ACM
1989. "Difusión de métodos de ingeniería de 30, núm. 3 (marzo): 195–96. Cartas de
software".Software IEEE, julio, 81–90. seguimiento en 30, no. 5 (mayo de 1987):
351–55; 30, núm. 6 (junio de 1987): 475–78;
Ramsey, H. Rudy, Michael E. Atwood y
30, núm. 7 (julio de 1987): 632–34; 30, núm. 8
James R. Van Doren. 1983. "Diagramas de flujo
(agosto de 1987): 659–62; 30, núm. 12
versus lenguajes de diseño de programas: una
(diciembre de 1987): 997, 1085.
comparación experimental".Comunicaciones de
la ACM26, núm. 6 (junio): 445–49. Sackman, H., WJ Erikson y EE Grant.
1968. "Estudios experimentales exploratorios que
Ratliff, Wayne. 1987. Entrevista enSolución
comparan el rendimiento de la programación en
Sistema.
línea y fuera de línea".Comunicaciones de la ACM11,
Raymond, ES 2000. “La Catedral y núm. 1 (enero): 3–11.
el bazar”,www.catb.org/~esr/writings/
cathedral-bazaar. Schneider, G. Michael, Johnny Martin y
WT Tsai. 1992. "Un estudio experimental
Raymond, Eric S. 2004.El arte de Unix Pro- de detección de fallas en los documentos
gramática. Boston, MA: Addison-Wesley. de requisitos del usuario",Transacciones
Rees, Michael J. 1982. “Evaluación automática ACM sobre Ingeniería y Metodología de
ment Aids for Pascal Programs.”Avisos de Software, volumen 1, núm. 2, 188–204.
ACM Signplan17, núm. 10 (octubre): 33–42. Schulmeyer, G. Gordon. 1990.Cero defectos
Reifer, Donald. 2002. “Cómo aprovechar al máximo Software. Nueva York, NY: McGraw-Hill.
Fuera de la Programación Extrema/ Sedgewick, Robert. 1997.Algoritmos en C,
Métodos Ágiles,”Actas, XP/Agile Partes 1-4, 3d ed. Boston, MA: Addison-
Universe 2002. Nueva York, Nueva York: Wesley.
Springer; 185–196.
Sedgewick, Robert. 2001.Algoritmos en C,
Reingold, Edward M. y Wilfred J. Hans- Parte 5, 3d ed. Boston, MA: Addison-
es 1983.Estructuras de datos. Boston, MA: Wesley.
Little, Brown.
Sedgewick, Robert. 1998.Algoritmos en C++,
Rettig, Marc. 1991. “Testing Made Palat- Partes 1-4, 3d ed. Boston, MA: Addison-
capaz.”Comunicaciones de la ACM34, núm. 5 Wesley.
(mayo): 25–29.
Sedgewick, Robert. 2002.Algoritmos en
Riel, Arthur J. 1996.Diseño orientado a objetos C++, Parte 5, 3d ed. Boston, MA: Addison-
Heurística. Reading, MA: Addison-Wesley. Wesley.
Rittel, Horst y Melvin Webber. 1973. Sedgewick, Robert. 2002.Algoritmos en Java,
"Dilemas en una teoría general de la Partes 1-4, 3d ed. Boston, MA: Addison-
planificación."Ciencias Políticas4:155–69. Wesley.
Robertson, Suzanne y James Robertson, Sedgewick, Robert. 2003.Algoritmos en Java,
1999.Dominar el proceso de requisitos. Parte 5, 3d ed. Boston, MA: Addison-
Reading, MA: Addison-Wesley. Wesley.
880 Bibliografía

SEI 1995.El modelo de madurez de la capacidad: Shneiderman, Ben y Richard Mayer.


Directrices para mejorar el proceso de 1979. "Interacciones sintácticas/semánticas en el
software, Instituto de Ingeniería de Software, comportamiento del programador: un modelo y
Reading, MA: Addison-Wesley, 1995. resultados experimentales".Revista internacional
de informática y ciencias de la información8,
SEI, 2003. “Perfil de Madurez del Proceso: Soft-
núm. 3: 219–38.
ware CMM®, CBA IPI and SPA Appraisal
Results: 2002 Year End Update”, Software Shneiderman, Ben. 1976. “Exploratory Ex-
Engineering Institute, abril de 2003. experimentos en el comportamiento del
programador”.Revista internacional de
Selby, Richard W. y Victor R. Basili.
informática y ciencias de la información5:123–43.
1991. "Análisis de la estructura del sistema
propenso a errores".Transacciones IEEE sobre Shneiderman, Ben. 1980.Psicología del software
ingeniería de softwareSE-17, núm. 2 (febrero): ogía: Factores humanos en sistemas
141–52. informáticos y de información. Cambridge,
MA: Winthrop.
SEN 1990. “Subsección de Sistema Telefónico
artículos,”Notas de ingeniería de software, abril Shneiderman, Ben. 1987.Diseñando al Usuario
de 1990, 11–14. Interfaz: Estrategias para una Interacción
Humano-Computadora Efectiva. Reading, MA:
Shalloway, Alan y James R. Trott. 2002.
Addison-Wesley.
Patrones de diseño explicados. Boston, MA:
Addison-Wesley. Shull, et al. 2002. “Lo que hemos aprendido
Acerca de la lucha contra los defectos”,Actas,
Sheil, BA 1981. “El Estudio Psicológico
Métricas 2002. IEEE; 249–258.
de Programación.”Informática Encuestas13,
núm. 1 (marzo): 101–20. Simón, Herbert. 1996.Las Ciencias del Ar-
Shen, Vincent Y., et al. 1985. “Identificación
oficial, 3d ed. Cambridge, MA: MIT
Software propenso a errores: un estudio
Press.
empírico”.Transacciones IEEE sobre ingeniería Simón, Herbert.La forma de la automatización para
de softwareSE-11, núm. 4 (abril): 317–24. Hombres y Gestión. Harper y Row,
Sheppard, SB, et al. 1978. “Predicting Pro- 1965.
la capacidad de los gramáticos para modificar el Simonyi, Charles y Martin Heller. 1991.
software”. TR 78-388100-3, General Electric “La revolución húngara”.BYTE, agosto,
Company, mayo. 131–38.
Sheppard, SB, et al. 1979. “Modern Cod- Smith, Connie U. y Lloyd G. Williams.
Prácticas de aprendizaje y desempeño del 2002.Soluciones de rendimiento: una guía
programador”.Computadora IEEE12, núm. 12 práctica para crear software adaptable y
(diciembre): 41–49. escalable. Boston, MA: Addison-Wesley.
Shepperd, M. y D. Ince. 1989. “Métricas, Consorcio de Productividad de Software. 1989.
Análisis de valores atípicos y el proceso de Calidad y estilo de Ada: pautas para
diseño de software”.Tecnología de la información programadores profesionales. Nueva York,
y el software31, núm. 2 (marzo): 91–98. NY: Van Nostrand Reinhold.
Shirazi, Jack. 2000.Ajuste de rendimiento de Java Soloway, Elliot y Kate Ehrlich. 1984.
En g. Sebastopol, CA: O'Reilly & “Estudios empíricos del conocimiento de la
Associates. programación”.Transacciones IEEE sobre
ingeniería de softwareSE-10, núm. 5
Shlaer, Sally y Stephen J. Mellor. 1988.
(septiembre): 595–609.
Análisis de sistemas orientados a objetos: modelado
del mundo en datos. Englewood Cliffs, Nueva Jersey: Soloway, Elliot y Sitharama Iyengar, eds.
Prentice Hall. 1986.Estudios Empíricos de Programadores.
Norwood, Nueva Jersey: Ablex.
Bibliografía 881

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.

Stevens, W., G. Myers y L. Constantine. Tripp, Leonard L., William F. Struck y


1974. "Diseño estructurado".Diario de sistemas Bryan K. Pflug. 1991. "La aplicación de
de IBM13, núm. 2 (mayo): 115–39. inspecciones de múltiples equipos en un
estándar de software crítico para la
Stevens, Wayne. 1981.Uso de Estructurado De-
seguridad",Actas del 4º Taller de Aplicación
señal. Nueva York, NY: John Wiley & Sons.
de Estándares de Ingeniería de Software,
Stroustrup, Bjarne. 1997.El programa C++- Los Alamitos, CA: IEEE Computer Society
idioma chino, 3d ed. Reading, MA: Press.
Addison-Wesley.
Departamento de Trabajo de EE.UU. 1990. “Resumen
Strunk, William y EB White. 2000.ele- de las perspectivas laborales de 1990–91”.
mentos de estilo, 4ª ed. Pearson. Perspectivas laborales trimestrales, primavera.
Sun Microsystems, Inc. 2000. “Cómo escribir Imprenta del Gobierno de los Estados Unidos.
Doc Comments for the Javadoc Tool”, 2000. Documento 1990-282-086/20007.
Disponible enhttp://java.sun.com/j2se/
javadoc/writingdoccomments/.
882 Bibliografía

Valett, J. y FE McGarry. 1989. “A Sum- Ward, William T. 1989. “Defecto de software


María de Experiencias de Medición de Prevención utilizando la métrica de complejidad
Software en el Laboratorio de Ingeniería de McCabe.Diario de Hewlett-Packard, abril, 64–
de Software.”Diario de Sistemas y Soft- 68.
mercancía9, núm. 2 (febrero): 137–48. Webster, Dallas E. 1988. “Mapping the De-
Van Genuchten, Michiel. 1991. “¿Por qué es firmar el Terreno de Representación de la Información.”
¿Software retrasado? Un estudio empírico de Computadora IEEE, del 8 al 23 de diciembre.
las razones del retraso en el desarrollo de
Semanas, Kevin. 1992. "¿Está hecho su código
software”.Transacciones IEEE en software Es-
¿Aún?"Lenguaje de ordenador, abril, 63–72.
ingenieriaSE-17, núm. 6 (junio): 582–90.
Weiland, Richard J. 1983.el del programador
Van Tassel, Dennie. 1978.estilo de programa,
Artesanía: construcción de programas,
Diseño, eficiencia, depuración y pruebas, 2ª
arquitectura informática y gestión de datos.
ed. Englewood Cliffs, Nueva Jersey: Prentice
Reston, VA: publicación de Reston.
Hall.
Weinberg, Gerald M. 1983. “Mata eso
Vaughn-Nichols, Steven. 2003. “Edificio ¡Código!"Infosistemas, agosto, 48–49.
Mejor software con mejores herramientas”,
Computadora IEEE, septiembre de 2003, 12–14. Weinberg, Gerald M. 1998.La psicología
de Programación Informática: Edición
Vermeulen, Allan, et al. 2000.Los elementos Aniversario de Plata. Nueva York, NY:
de estilo Java. Prensa de la Universidad de
Dorset House.
Cambridge.
Weinberg, Gerald M. y Edward L. Schul-
Vessey, Iris, Sirkka L. Jarvenpaa y Noam hombre. 1974. "Objetivos y rendimiento en la
Tractinsky. 1992. “Evaluación de productos de
programación de computadoras".Factores
proveedores: herramientas CASE como
humanos16, núm. 1 (febrero): 70–77.
compañeros metodológicos”.Comunicaciones
de la ACM35, núm. 4 (abril): 91–105. Weinberg, Gerardo. 1988.Repensando Sistemas
Análisis y Diseño. Nueva York, NY:
Vessey, Iris. 1986. “Experiencia en depuración
Dorset House.
Programas de computadora: un análisis del
contenido de los protocolos verbales”. Weisfield, Matt. 2004.El orientado a objetos
Transacciones IEEE sobre sistemas, hombre y Proceso de pensamiento, 2ª ed. SAM, 2004.

cibernéticaSMC-16, no. 5 (septiembre/ Weiss, David M. 1975. “Evaluating Soft-


octubre): 621–37. Desarrollo de software por análisis de errores:

Votta, Lawrence G., et al. 1991. “Investiga- los datos del centro de investigación de

ing la Aplicación de Técnicas de Captura- arquitectura”.Revista de Sistemas y Software

Recaptura a Requerimientos y Revisiones 1, no. 2 (junio): 57–70.


de Diseño.”Actas de los Seis- décimo taller Weiss, Eric A. 1972. “Revisión deel psicol-
anual de ingeniería de software, 4 y 5 de ogía de la programación informática, por Gerald M.
diciembre de 1991. Greenbelt, MD: Centro Weinberg”.Evaluaciones de informática ACM
de Vuelo Espacial Goddard. Documento 13, núm. 4 (abril): 175–76.
SEL-91-006.
Wheeler, David, Bill Brykczynski y Regi-
Walston, CE y CP Félix. 1977. “Un nald Meeson. 1996.Inspección de software:
Método de Programación de Medición y una mejor práctica de la industria. Los
Estimación.”Diario de sistemas de IBM16, Alamitos, CA: IEEE Computer Society Press.
núm. 1: 54–73.
Whittaker, James A. 2000 “¿Qué es Soft-
Ward, Roberto. 1989.Introducción de un programador ¿Pruebas de artículos? ¿Y por qué es tan
ducción a la depuración de C. Lawrence, KS: difícil?” Software IEEE, enero de 2000, 70–79.
Publicaciones de I + D.
Bibliografía 883

Whittaker, James A. 2002.como romper Youngs, Edward A. 1974. “Human Errors in


Software: una guía práctica para las pruebas. Programación."Revista internacional de
Boston, MA: Addison-Wesley. estudios hombre-máquina6:361–76.
Whorf, Benjamín. 1956.lenguaje, pensamiento Yourdon, Edward y Larry L. Constan-
y Realidad. Cambridge, MA: MIT Press. púa. 1979.Diseño estructurado: fundamentos de
una disciplina de diseño de sistemas y
Wiegers, Karl. 2002.Peer Reviews en Soft-
programas informáticos. Englewood Cliffs,
ware: una guía práctica. Boston, MA:
Nueva Jersey: Yourdon Press.
Addison-Wesley.
Yourdon, Edward, ed. 1979.Clásicos en Soft-
Wiegers, Karl. 2003.Requisitos de Software,
ingeniería de mercancías. Englewood Cliffs, Nueva Jersey:
2d ed. Redmond, WA: Microsoft Press.
Yourdon Press.
Williams, Laurie y Robert Kessler. 2002.
Yourdon, Edward, ed. mil novecientos ochenta y dos.escritos de la
Par Programación Iluminado. Boston,
Revolución: lecturas seleccionadas sobre ingeniería
MA: Addison-Wesley.
de software. Nueva York, Nueva York: Yourdon
Willis, Ron R., et al. 1998. “Hughes Air- Press.
craft's Widespread Deployment of a
Continuously Improving Software Process”,
Tu don, Edward. 1986a.Manejando el
Técnicas estructuradas: estrategias para el
Instituto de Ingeniería de Software/
desarrollo de software en la década de 1990, 3d ed.
Universidad Carnegie Mellon, CMU/SEI-98-
Nueva York, Nueva York: Yourdon Press.
TR-006, mayo de 1998.
Tu don, Edward. 1986b.Naciones en riesgo.
Wilson, Steve y Jeff Kesselman. 2000.
Nueva York, Nueva York: Yourdon Press.
Rendimiento de la plataforma Java: estrategias y
tácticas. Boston, MA: Addison-Wesley. Tu don, Edward. 1988. “Los 63 mejores
Libros de software”.programador estadounidense,
Wirth, Niklaus. 1995. “Una súplica por Lean Soft-
Septiembre.
mercancía,"Computadora IEEE, febrero de 1995.
Tu don, Edward. 1989a.Estructura moderna
Wirth, Niklaus. 1971. “Program Development-
Análisis turado. Nueva York, Nueva York: Yourdon
ment by Stepwise Refinement.”
Press.
Comunicaciones de la ACM14, núm. 4
(abril): 221–27. Tu don, Edward. 1989b.Caminata Estructurada-
pasantes, 4ª ed. Nueva York, Nueva York:
Wirth, Niklaus. 1986.Algoritmos y Datos
Yourdon Press.
Estructuras. Englewood Cliffs, Nueva Jersey: Prentice
Hall. Tu don, Edward. 1992.Declive y caída de
el programador americano. Englewood Cliffs,
Woodcock, Jim y Martin Loomes. 1988.
Nueva Jersey: Yourdon Press.
Ingeniería de Software Matemáticas.
Reading, MA: Addison-Wesley. Zachary, Pascual. 1994.sensacional!los
Prensa Libre.
Woodfield, SN, HE Dunsmore y VY
Shen. 1981. "El efecto de la modularización Zahniser, Richard A. 1992. “A Massively
y los comentarios sobre la comprensión Enfoque de desarrollo de software
del programa".Actas de la Quinta paralelo”.programador estadounidense,
Conferencia Internacional de Ingeniería de enero, 34–41.
Software, marzo de 1981, 215–23.
Wulf, WA 1972. “A Case Against the GO-
A."Actas de la 25ª Conferencia Nacional
ACM, agosto de 1972, 791–97.
Índice

Símbolos y Números rutinas de acceso ejemplos de operaciones, tabla de,


* (símbolo de declaración de puntero), 332, abstracción beneficio, 340 129–130
334–335, 763 abstracción, nivel de, 341–342 transmisión de datos, minimización de,

& (símbolo de referencia de puntero), 332 ventajas de, 339–340 128


– > (símbolo de puntero), 328 beneficio de variables barricadas, mejoras de rendimiento con,
regla 80/20, 592 339 control centralizado de, 339 128
creación, 340 propósito de, 126
g_ guía de prefijo, 340 beneficio de entidades del mundo real, trabajando con,
A ocultar información, 340 falta de 128–129
abreviatura de nombres, 283–285 apoyo para, superación, pregunta de representación, 130
tipos de datos abstractos.VerPatrón 340–342 elementos simples como, 131
ADTs Abstract Factory, 104 bloqueo, 341 verificación de beneficio de código,
abstracción paralelismo de, 342 128 desarrollo ágil, 58, 658 identidades
rutinas de acceso para, 340–342 ADT requiriendo, 340 algebraicas, 630
para.Veranalogía de la esclusa de aire problemas accidentales, 77–78 acumulación de algoritmos
ADT, 136 lista de verificación, 157 una metáfora del sistema, 15–16 precisión, 464 comentando, 809
heurística comparada con, 12
clases para, 152, 157 ada metáforas que sirven como, 11–12
cohesión con, 138 descripción de, 63 recursos en, 607
complejidad, para manejo, 839 orden de parámetros, 174–175 rutinas, planificación para, 223
nivel consistente para clase adaptabilidad, 464 aliasing, 311-316
interfaces, 135–136 Patrón adaptador, 104 desarrollo de habilidades de análisis,
definido, 89 además, peligros de, 295 ADT 823 enfoques para el desarrollo
erosión bajo modificación (tipos de datos abstractos) desarrollo ágil, 58, 658 enfoques
problema, 138 abstracción con, 130 ascendentes, 112–113,
evaluar, 135 rutinas de acceso, 339–342 697–698
meta de exactitud, 136–137 formando beneficios de, 126–129 Programación extrema, 58,
consistentemente, 89–90 buen ejemplo los cambios no propagan el beneficio, 471–472, 482, 708, 856
para interfaces de clase, 128 importancia de, 839–841
133–134 clases basadas en, 133 ejemplo de sistema enfoque iterativo.Veriteración en
pautas para crear clases de enfriamiento, 129–130 datos, significado desarrollo
interfaces, 135–138 de, 126 problema de optimización prematura,
términos de dominio de problemas de alto nivel, definido, 126 840
847 beneficio de documentación, 128 control de calidad, 840.Ver también
estructuras de implementación, instanciación explícita, 132 expedientes calidad del software
bajo nivel, 846 como, 130 recursos para, 58–59
inconsistente, 135–136, 138 directrices, 130–131 enfoque secuencial, 35–36 procesos
interfaces, metas para, 133–138 ocultando información con, 127 de equipo, 839–840 enfoques de
niveles de, 845–847 instanciación, 132 arriba hacia abajo, 111–113,
opuestos, pares de, 137 instanciación implícita, 132 694–696
nivel OS, 846 interfaces, haciendo más arquitectura
patrones para, 103 informativo, 128 definición de bloque de construcción, 45
colocar elementos en árboles de herencia, tipos de datos de bajo nivel como, 130 reglas de negocio, 46
146 independencia de los medios con, 131 comprar vs construir componentes,
mal ejemplo para las interfaces de clase, instancias múltiples, manejo, 51
134–135 131–133 cambios, 44, 52
términos del dominio del problema, bajo nivel, necesidad de, ejemplo de, 126–127 lista de verificación para, 54–55
846 lenguajes no orientados a objetos diseño de clase, 46
nivel de lenguaje de programación, 846 con, 131–133 estrategia de retraso del compromiso,
rutinas para, 164 objetos como, 130 52 integridad conceptual de, 52

885
886 justo oe
pt-ilceveexlpere
nstsryiones

arquitectura,continuado ajuste de rendimiento, 593–594, Principio de sustitución de Liskov,


diseño de datos, 46 603–604 144–145
definido, 43 refactorización, 572 anulable frente a no anulable
manejo de errores, 49–50 referencias, minimización, 626–627 rutinas, 145–146
tolerancia a fallas, 50 prefijos semánticos para, 280–281 datos protegidos, 143
GUI, 47 pruebas centinela para bucles, 621–623 rutinas anuladas para hacer
importancia de, 44 guía de acceso secuencial, 310 lenguaje nada, 146–147
entrada/salida, 49 ensamblador Clases sueltas desde, 146 Básico,
internacionalización planificación, 48 descripción de, 63 65.Ver tambiénPruebas básicas de
interoperabilidad, 48 herramientas de listado, 720 Visual Basic, estructuradas, 503,
punto clave para, 60 planificación recodificación a, 640–642 505–509
de la localización, 48 independencia afirmaciones Tipo BCD (decimal codificado en binario),
de la máquina, 53 ingeniería programa de aborto recomendado, 297
excesiva, 51 206 BDUF (gran diseño al frente), 119
porcentaje de la actividad total, por tamaño de argumentos a favor, 189 belleza, 80
proyecto, 654–655 supuestos a comprobar, lista de, 190 pares de inicio y fin, 742–743
objetivos de rendimiento, 48 barricadas, relación con, 205 beneficios bibliografías, software, 858 integración
orientado al desempeño, 590 de, 189 big-bang, 691 diseño grande por
naturaleza de requisito previo de, 44 construyendo tu propio mecanismo adelantado (BDUF), 119 búsquedas
organización del programa, 45–46 para, 191 binarias, 428
calidad, 52–53, 55 ejemplo de C++, 191 Unión
gestión de recursos, 47 recursos uso peligroso del ejemplo, 192 en código, 252
en desarrollo, 57 decisiones de definido, 189 tiempo de compilación, 252–253
reutilización, 52 dependencias, comprobación de, 350 diseño heurístico con, 107
áreas de riesgo, identificación, 53 manejo de errores con, 191, 193–194 justo a tiempo, 253
escalabilidad, 48 código ejecutable en, 191–192 punto clave, 258
diseño de seguridad, 47 directrices para, 191–193 tiempo de carga, 253

factibilidad técnica, 51 Ejemplo de Java de verificación de tiempo de ejecución, 253

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

directrices de evaluación, 438–440 etiquetado, 381 efectos secundarios, 759–761


funciones, pasar a, 434–435 múltiplo en un bucle, 380 archivos fuente, diseño en, almacenamiento en
identificadores para, 431–433 anidado-si simplificación con, caché 773, ajuste de código con, modelo de
si declaraciones, negativos en, 446–447 madurez de capacidad (CMM) 628–629,
435–436 while bucles con, 379 falla de 491
comparaciones implícitas, 433 puente, Tacoma Narrows, 74 Patrón capturar el trabajo de diseño, 117–118 Regla
Sintaxis de Java, 439, 443 pautas de puente, 104 cardinal de la evolución del software,
de diseño, 749–750 identidades depuración de fuerza bruta, 548–549 565
lógicas, 630 desbordamientos de búfer, 196 CASE (software asistido por computadora)
negativos en, 435–437 insectos.Verdepuración; defectos en el código; ingeniería) herramientas, 710
numérico, estructuración, 440– errores declaraciones de casos
441 paréntesis para aclarar, construir herramientas, 716–717.Ver también orden alfa, 361
437–438 compiladores lista de control, 365
punteros, comparaciones con, 441 metáfora de la construcción, 16–19 construcción frente a depuración, 206
forma positiva recomendada, componentes de compra, 18 construcciones, cláusulas supletorias, 363
435–437 diariamente.Verconstrucción diaria y accesos directos, 363–365
refactorización, 572 pruebas de humo declaraciones de fin de caso, 363–365
evaluación de cortocircuito, 438–440 reglas del negocio disposición final, 751–752
simplificación, 433–435 requisitos previos de arquitectura, 46 detección de errores en, 363
variables enVervariables booleanas cambio, identificación de áreas de, 98 frecuencia de orden de ejecución,
cero, comparaciones con, 441–442 tabla de buenas prácticas para, 31–32 361, 612–613
funciones booleanas diseño de subsistema, 85 declaraciones if, comparando
creando a partir de expresiones, compra de componentes, 18, 51 actuación con, 614
434–435 puntos clave, 366
declaraciones if, usadas en, 359 soporte de idioma para, 361
pruebas booleanas
C ifs anidados, conversión de,
irrumpir en pruebas parciales, lenguaje C 448–449, 451
433 esconderse con rutinas, 165 ADT con, 131 primera regla del caso normal,
sintaxis de expresiones booleanas,
simplificar, 301–302 361 orden numérico, 361
cero, comparaciones con, 441–442
442–443 pedidos de casos, 361
variables booleanas
descripción de, 64 modificaciones paralelas a,
convenciones de nomenclatura para, 275,
0 y 1 como valores, 432 C, creando tipo 566 variables falsas, 361–362
278 punteros, 334–335
de datos, 302–303 lista de verificación, polimorfismo preferible a,
tipos de datos de cadena, 299–301, 317
317 147–148
errores de índice de cadena, 299–300 C#,
documentación con, 301 tipos rediseño, 453
64
enumerados como alternativa, refactorización, 566, 573
304 C++ guía de acción simple, 361 métodos
ejemplo de afirmación, 191
expresiones con.Verbooleano basados en tablas usando,
sintaxis de expresiones booleanas,
expresiones 421–422
identificadores para, 431–433
442–443 cambio de control.Verconfiguración
denominación, 268–269
depuración de stubs con, 208–209 administración
pruebas de simplificación con, 301–302 ceros y
descripción de, 64 matrices de caracteres, 299–300.Ver también
unos como valores, 432 prueba de preparación
macros DoNothing(), 444–445 tipos de datos de cadena

del jefe sobre requisitos previos,


excepciones en, 198–199 tipos de datos de caracteres
30–31 rutinas en línea, 184–185 matrices frente a punteros de cadena,
enfoque de abajo hacia arriba para el diseño,
consideraciones de interfaz, 139–141 299 Lenguaje C, 299–301
112–113 diseño recomendado, 745 conjuntos de caracteres, 298
macro rutinas, 182–184 convenciones de
integración ascendente, 697–698 lista de verificación, 316–317
nomenclatura para, 275–277 sentencias
análisis de límites, 513–514 llaves estrategias de conversión, 299
nulas, 444–445
caracteres mágicos (literales),
parámetros, por referencia vs. por
diseño de bloque con, 740–743 297–298
estilos comparados, 734
valor, 333 Unicode, 298, 299
punteros, 325, 328–334, 763
romper declaraciones carácter, personal
preprocesadores, excluyendo depuración
Bucles de C++, 371–372 capacidad de análisis, 823
código, 207–208
precaución sobre, 381 habilidades comunicativas, 828
directrices, 379–380 recursos para, 159
888 entrada fcihrsetctkolipst-slevel

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

arreglos, 317 estructuras, 343 funciones en.Verfunciones;


copias de seguridad, 670 métodos basados en tablas, 429 rutinas
expresiones booleanas, 459 pruebas, 503, 532 datos globales, ocultación, 153
declaraciones de casos, 365 herramientas, 70 clases de dios, 155
tipos de datos de caracteres, 316– creación de tipos, 318 enfoque de piratería para, 233 ocultar
317 clases, 157–158, 233–234, variables, 257–258, 288–289, detalles de implementación,
578–579, 774, 780 343–344 153
prácticas de codificación, 69 dependencias circulares, 95 lista de verificación de implementación, 158
ajuste de código, 607–608, 642–643 clases llamadas indirectas a otras clases, 150
comentarios, 774, 816–817 tipos de datos abstractos.VerADT objetos ocultación de información, 92–93
declaraciones condicionales, abstractos, modelado, 152 lista de herencia, 144–149, 158
gestión de configuración 365, verificación de abstracción, 157 inicialización de miembros,
669–670 alternativas a PPP, 232–233 requisitos 243 integración, 691, 694,
constantes, 317 previos de arquitectura, 46 suposiciones 697 clases irrelevantes, 155
prácticas de construcción, 69–70 sobre los usuarios, 141 base.Verclases es una relación, 144 puntos
estructuras de control, 459, 773, 780 base clave para, 160, 234
último topc-oledveetluennitnrg y 889

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

cohesión creación eficiente de, 788–791 soluciones alternativas, 800

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

estándares, IEEE, 497 795–801, 816 optimización de código complicado,


pruebas, en comparación declaraciones de parámetros, 806–807 597 validadores con, 231
con 481 recorridos, 492–493 partes de programas, 809 advertencias, 293, 557
colecciones, refactorización, 572 consideraciones de rendimiento, 791
construcción schlaesdtutloep
s,-elesvtiem y
titnrg
inclinarse 891

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

codificación para reducir, puntos clave, 366 colaborativo.Verdecisiones de


839 consultar tablas, sustituir, colaboración.Verconstrucción
contribuciones de la estructura de control a, 614–615 decisiones
456–459 bucle, condicional.Verbucles directrices, 66
convenciones para la gestión, caso normal primera pauta, gerente.Vergerente
844–845 356–357 construcción
puntos de decisión, contar, 458 ruta normal primera pauta, 355 porcentaje de la actividad total, por tamaño de
importancia de, 457 cláusulas if nulas, 357 proyecto, 654–655
aislamiento, clases para, 153 enunciados simples si-entonces, 355–357 requisitos previosVerrequisitos previos,
tiempo en vivo, 459 refactorización, 573 río arriba
gestión, 77–79, 844–845 Métrica de evaluación de cortocircuito, declaraciones calidad de.Vercalidad de los recursos
McCabe, 457–458 objetos mentales de interruptor 610.Vercaso de software en, 856
sostenidos, medida de, declaraciones horarios, estimar.Ver
457 depuración confesional, gestión de cronogramas de construcción,
métodos para el manejo, 837–839 configuración 547–548 estimación
objetivo de minimización, 80 anticipación arquitectónica de tamaño de los proyectos, efectos sobre.VerTalla
patrones, reduciendo con, 103 dominio cambiar, 52 de proyectos
del problema, trabajando en, 845 planes de respaldo, 669, 670 tableros, herramientas para.Verherramientas de

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

tamaño de los proyectos, efecto sobre, 670 prácticas, 69–70


656–657 cambios de código, 667–668 lista de verificación de prácticas de
lapso, 459 costo, estimar, 666 codificación, 69 entornos de onda
prueba de componentes, 499 definido, 664 temprana, 67 puntos clave para, 70
componentes, compra, 18, 51 cambios de diseño, 666–667 estimación de principales prácticas de construcción,
Patrón compuesto, 104 costos de cambio, 666 agrupación de seleccionando, 69–70
límites compuestos, 514 declaraciones solicitudes de cambio, 666 grandes entornos tecnológicos maduros,
compuestas.Verbloquea los calificadores volúmenes de cambio, 666 identificación de 67
de valor calculado de áreas de cambio, 97–99 configuraciones de convenciones de programación, 66–
nombres de variables, 263–264 máquinas, 66 programación en lenguajes,
software asistido por computadora reproducción, 668 68–69
herramientas de ingeniería (CASE), propósito de, 664–665 lenguajes de programación.Ver
declaraciones condicionales 710 cambios de requisitos, 41, 664, lista de verificación de garantía de calidad de

llamadas a funciones booleanas con, 359 666–667 elección de lenguaje de programación, 70 lista de

variables booleanas recomendadas, recursos en, 670 verificación de trabajo en equipo, 69

301–302 SMC, 665 ondas tecnológicas, determinando


declaraciones de casos.Vercaso control de versión de herramienta, su ubicación en, 66–69 lista de

declaraciones software de control de versión 668, verificación de herramientas, 70

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

cronogramas de construcción, estimación, pares de inicio y fin sin sangría, 746 D


continuado inusual, descripción general de, 408 pruebas diarias de construcción y humo
resumen, 671 convenciones, codificación automatización de, 704
planificación estimación tiempo, beneficios de, 844–845 beneficios de, 702
671 reducción de alcance, 676 lista de control, 69 compilaciones rotas, 703, 705
reestimando, 672 formatoVerdiseño grupos de compilación, 704
especificación de requisitos, 672 peligros, evitando con, 844 beneficio de lista de control, 707
recursos para, 677 previsibilidad, 844 tipos de datos de definido, 702
equipos, en expansión, 676 conversión, 635 habilidades de beneficio diagnóstico, 702
constructores cooperación, importancia de, área de espera para adiciones,
copias profundas vs. superficiales, 151–152 828 704–705
excepciones con, 199 corrección, 197, 463 importancia de, 706
directrices para, 151–152 inicialización costosVer tambiénla optimización del rendimiento comunicados matutinos, 705
de miembros de datos, 151 cambiar estimaciones, 666 presión, 706
refactorización, 577 beneficios de colaboración, 480–481 requisito de prueba previa, 704
propiedad singleton, cumplimiento, 151 depuración, tiempo consumido por, revisiones, 704
clases de contenedor, 310 474–475 pruebas de humo, 703
contención, 88, 143 defectos que contribuyen a, 519–520 trabajo sin asfaltar, 702
líneas de continuación, 754–758 detección de defectos, 472 datos
declaraciones de continuación, 379, 380, rutinas propensas a errores, requisitos previos de arquitectura, 46
381 integración continua, 706 518 estimación, 658, 828 malas clases, pruebas para, 514–515
Estructuras de Control reparación de defectos, 472–473, 519 cambio, identificación de áreas de, 99
expresiones booleanas enVer Principio general del software ajuste de código.Verdatos
expresiones booleanas Calidad, 474–475, 522 transformaciones para el ajuste de
caso.Verlistas de verificación de programación en pareja vs. inspecciones, código
declaraciones de casos, 459, 773, 780 480–481 estados combinados, 509–510
comentarios, 804–805, 817 recursos en, 658 estado definido, 509–510
complejidad, contribuciones a, bucles contados.Verpara acoplamiento caminos usados definidos, pruebas,
456–459 de bucles 510–512
sentencias compuestas, 443 flujo clases base a clases derivadas, diseño, 46
condicional.Vercondicional 143 entró en estado, 509
declaraciones clases, demasiado apretado, 142–143 estado salido, 509
líneas de continuación en, 757 consideraciones de diseño, 100–102 buenas clases, pruebas, 515–516
tipos de datos, relación con, flexibilidad de, 100–101 estado muerto, 509–510
254–255 goles de, 100 legado, compatibilidad con, 516 errores
documentación, 780 suelto, 80, 100–102 de caso nominal, 515 prueba,
pares de inicio y fin con doble sangría, tipo de objeto-parámetro, 101 generadores para, 524–525 tipos.Ver
746–747 tipo semántico, 102 tipos de datos utilizados estado, 509–
ve a S.Versentencias goto if tipo de parámetro de datos simple, 101 510
sentencias.Veriteración de tipo de objeto simple, 101 diccionarios de datos, 715
sentencias if, 255, 456 tamaño de, 100 prueba de flujo de datos, 509–512 prueba
puntos clave, 460 visibilidad de, 100 de alfabetización de datos, 238–239
estilos de diseño, 745–752 cobertura herramientas de registro de datos, 526
buclesVerbucles herramientas de seguimiento, 526 estructuras de datos.Verestructuras
múltiples retornos de rutinas, prueba de base estructurada, 505–509 transformaciones de datos para código
391–393 CRC (clase, responsabilidad, Afinación
sentencias nulas, 444–445 Colaboración) tarjetas, 118 minimización de la dimensión de la matriz,
recursivo.Verconfiabilidad de la creatividad, importancia de, 829, 857 625–626
recursión correlacionada con herramientas de referencia cruzada, 713 minimización de referencia de matriz,
complejidad, 457 curiosidad, rol en el personaje, 822–825 626–627
vuelve como.Verdeclaraciones de retorno Tipos de datos de moneda, 297 almacenamiento en caché de datos, 628–629

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

tipos de datos cambios, recientes, 547 depuradores simbólicos, 526–527


convención de prefijo "a", 272 tipos lista de verificación, 559–561 verificación de sintaxis, 549–550, 557,
de datos abstractos.VerMatrices de comentarios, fuera de lugar, 550 listas de 560
ADT.Verarreglos defectos comunes, 547 compiladores como depuradores del sistema, 558
BC, 297 herramientas para, 549, 557 depuración creación de casos de prueba, 544 pruebas, en
booleanoVercambio de variables confesional, 547–548 costos de, 29–30, 474– comparación con, 500 tiempo para,
booleanas, identificando áreas de, 99 475 establecimiento de máximos, 549 herramientas
caracteres.Verdatos de carácter herramientas de depuración, 526–527, 545, para, 526–527, 545, 556–559,
tipos 556–559, 719.Ver también 719.Ver tambiénayudas de depuración para
lista de verificación, 316–318 ayudas de depuración comprender los problemas, 539 pruebas
complejo.Verestructuras estructuras defectos como oportunidades, 537–538 unitarias, 545
de control, relación con, defensivos.Verayudas de depuración casos de prueba variables, 545 advertencias,
254–255 definidas, 535 tratamiento como errores, 557 ayudas de
creandoVercreación de tipos Herramienta de diferencias, 556 depuración
Moneda, 297 perfiladores de ejecución para, 557– Preprocesadores de C++, 207–208
definiciones, 278 558 expandir regiones sospechosas, declaraciones de casos, 206
tipos enumerados.Ver 547 se recomienda la introducción temprana,
tipos enumerados experiencia de los programadores, 206
punto flotante.Verpunto flotante efectos de, 537 programación ofensiva, 206
tipos de datos detección de defectos, 540, 559–560 eliminación de planificación de, 206–
números enterosVertipos de datos enteros reparación de defectos, 550–554 209 punteros, comprobación, 208–
datos iterativos, 255 adivinando, 539 209 preprocesadores, 207–208
puntos clave para, 318 denominación, historia de, 535–536 restricciones de producción en
273, 277, 278 numérico.Verprimitivos prueba de hipótesis, 543–544, 546 versiones de desarrollo, 205
sobrecargados de tipos de datos enfoque incremental, 547 enfoque propósito de, 205
numéricos, 567 punteros.Verpunteros ineficaz para, 539–540 puntos clave, talones, 208–209
562 herramientas de control de versiones, 207
refactorización a clases, 567, 572 números de línea de compiladores, 549 herramienta tablas de decisión.Verbasado en tablas
recursos en, 239 de pelusa, 557 métodos
datos selectivos, 254 enumerar posibilidades, 546 localizar fuentes declaraciones
datos secuenciales, 254 de error, 543–544 herramientas de comentando, 794, 802–803, 816
instrumentos de cuerda.Verestructuras de comprobación lógica, 557 const recomendado, 243
tipos de datos de cadena.Verestructuras t_ varios mensajes del compilador, 550 declarar y definir cerca del primer uso
convención de prefijos, 272 definida por el búsquedas de código de restricción, 546 regla, 242–243
usuario.Vertipo creación variables de, correcciones obvias, 539 definir cerca de la regla del primer uso,

diferenciando de, variaciones de rendimiento, 536–537 configuraciones 242–243


272–273 de compiladores de todo el proyecto, último recomendado, 243
bases de datos 557 formato, 761–763
problemas de rendimiento, 601 consideraciones psicológicas, declaraciones implícitas, 239–240
SQL, 65 554–556 múltiplo en una línea, 761–762
diseño de subsistemas, 85 calidad del software, rol en, 536 denominación.Verconvenciones de
refactorización a nivel de datos, 571–572, comillas, fuera de lugar, 550 mejoras nomenclatura datos numéricos,
577 días del mes, determinación, de legibilidad, 538 enfoque comentarios, 802 orden de, 762
413–414 recomendado, 541 reexaminación colocación de, 762
desasignación del código propenso a defectos, punteros, 325–326, 763
declaraciones goto para, 399 547 usando todo declarado,
punteros, de, 326, 330, 332 recursos para, 561 257 Patrón Decorator, 104
declaraciones Debug.Assert, 191–193 Ayudantes de Satanás, 539– defectos en el código
depuración 540 andamios para, 558 clases propensas a errores, 517–518
ayudas a.Verayudas de depuración método científico de, 540–544 clasificaciones de, 518–520 errores
búsquedas binarias de código, 546 autoconocimiento de, 538 administrativos (errores tipográficos),
ceguera, fuentes de, 554–555 puntos de comparadores de código fuente, 519 Código Ejemplo completo,
interrupción, 558 556 errores de estabilización, 542– 490–491
descansos, tomando, 548 543 enfoques supersticiosos, construcción, proporción
fuerza bruta, 548–549 539–540 resultante de, 520–521
Traducido del inglés al español - www.onlinedoctranslator.com

894 fdie tosp


rsften rimando
iv-elepvreolgernatm

defectos en el código,continuado problemas de seguridad, 212 técnica divide y vencerás,


costo de detección, 472 costo guía de errores triviales, 209 111
de reparación, 472–473 bases entrada de validación, 188 documentacion, como, 781
de datos de, 527 estado de datos definido, 509–510 variables exceso de documentación, 117
detección por diversas técnicas, de definición.Verdeclaraciones Delphi, naturaleza emergente de, 76
mesa de, 470 recodificación a ensamblador, encapsulación, 90–91
distribución de, 517–518 facilidad 640–642 suficiente, determinante, 118–119
para corregir defectos, 519 listas de Los teoremas de DeMorgan, aplicando, problemas esenciales, 77–78 objetivo de
verificación de errores, 489 436–437 extensibilidad, 80
tasa esperada de, 521–522 hallazgo, dependencias, ordenamiento de código formalidad de, determinar,
lista de verificación, 559–560 fijación. herramientas de verificación, 716 115–117
Verdepuración; fijación circular, 95 formalización de contratos de clase, 106 lista de

defectos aclaración, 348–350 verificación de objetivos, 122–123

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>

matrices para, 414 perros y ponis, 495 campos de etiquetas 139–141


enfoque de declaración de caso, de identificación, 326–327 macros miembros de datos públicos, 567
421–422 DoNothing(), 444–445 DRY (No te miembros públicos de clases, 139
ejemplo de días en el mes, 413–414 repitas) rutinas públicas en interfaces
definido, 413 principio, 565 preocupación, 141

método de diseño para, ejemplo de duplicación violaciones semánticas de, 141–142


formato de mensaje flexible 420, evitar con rutinas, 164–165 código débil, 567
416–423 como indicador de refactorización, 565 bucles sin fin, 367, 374 comentarios de
falsificación de claves para, 423–424 línea final, 793–795 diseño de línea final,
ejemplo de tasas de seguro, 415–416 claves 743–745, 751–752,
para, 423–424
mi 767
entornos de onda temprana, 67 objetivo de
enfoque de objeto, 422–423 tipos enumerados
diseño de facilidad de mantenimiento, 80
claves de transformación, 424 beneficios de, 303
eclecticismo, 851–852
desmontadores, 720 booleanos, alternativa a, 304
disciplina, importancia de, 829 herramientas de edición
C++, 303–304, 306
embellecedores, 712
reglas del discurso, 733 cambio beneficio, 304
generadores de jerarquía de clases, 713
deshacerse de objetos, 206 divide y lista de control, 317
herramientas de referencia cruzada, 713
vencerás técnica, 111 división, 292– comentarios en sustitución de,
293 Herramientas diferenciales, 712
802–803
Haz bucles, 369–370.Ver también
grep, 711 creando para Java, 307
documentación de bucles
IDE, 710–711 definido, 303
documentación de la interfaz, 713
abreviatura de nombres, 284–285 emulación por variables globales, 338
herramientas de combinación, 712
ADT para, 128 trampas de valor explícito, 306
búsquedas de cadenas de archivos múltiples,
mal código, de, 568 truco de primera entrada inválida,
Book Paradigm for, 812–813
711–712 305–306 iterando, 305
captura de trabajo, 117–118 listas
plantillas, 713 Java, creando para, 307 idiomas
de verificación, 780–781, 816–817
eficiencia, 464 disponibles en, 303 límites de
regla de ochenta/veinte (80/20), 592
clases, 780 bucle con, 305
cláusulas else
comentariosVercomentarios nombrando, 269, 274, 277–279
llamadas a funciones booleanas con, 359
estructuras de control, 780 tarjetas parámetros usando, 303
declaraciones de casos en lugar de, 360
CRC para, 118 dependencias, legibilidad de, 303
cadenas, en, 358–360
clarificación, 350 beneficio de confiabilidad, 304
896 Feiq
rsutatlo
itpy-, lfelo
vealtienngt-rpyoint

tipos enumerados,continuado factores que influyen, 674–675 preprocesadores, 718–719


estándar para, 306 nivel de detalle para, 672 herramientas de configuración, 718

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

fuentes de, tabla, 518 herramientas de construcción, 716–717


tolerancia a fallas, 50
integración orientada a características,
problemas esenciales, 77–78 bibliotecas de código, 717

estimación de horarios asistentes de generación de código,


700–701
enfoques a, lista de, 671 compiladores 718.Verherramientas de
Números de Fibonacci, 397–398

costos de cambio, 666 instalación de compiladores, 718 enlazadores,


cifras, lista de, xxxiii

controlar, en comparación con, 675 716


lasg
atoto
p-slteavtelmeenntrtys 897

archivos bucles foreach, 367, 372 GRAMO

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

recursos para, 409–410 declaraciones if


verificación de objetivos, 122–123
llamadas a funciones booleanas con,
reescrito con ifs anidados, pautas para usar, 109–110
359 romper bloques, simplificación con,
402–403 jerarquías para, 105–106
reescrito con variables de estado, ocultación de información, 92–97, 446–447
403–404 120 herencia, 91–92 declaraciones de caso, en comparación con,

reescrito con try-finally, interfaces, formalizándose como


360, 614
404–405 contratos, 106 sentencias de caso, conversión a,

ejemplo de reescritura trivial, 400–401 práctica de iteración, 111–117


448–449, 451
etiquetas no utilizadas, 408 puntos clave, 123
cadenas de, 358–360
lista de control, 365
herramientas de diseño gráfico, nivel de detalle necesario, 115–117
primera pauta de casos comunes,
710 grep, 711 modularidad, 107
creciendo una metáfora del sistema, sugerencia de enfoque múltiple, 359–360
líneas de continuación en, 757
14-15 GUI (interfaces gráficas de usuario) 110
cubriendo todos los casos, 360
requisitos previos de arquitectura, 47 naturaleza del proceso de diseño, 76
cláusulas else, 358–360, 406–407
datos de refactorización de, 576 base no determinista para, 87 orientado
igualdad, bifurcación, 355 ejemplos de
diseño de subsistema, 85 a objetos, recurso para, 119 objetos,
procesamiento de errores,
mundo real, encontrar, 87–89 patrones,
103–105, 120
356–357
H prácticas, 110–118, 122 creación
factorización de rutinas, 449–451
hábitos de los programadores, 833–834 invertido, 358
de prototipos, 114–115
enfoque de piratería para el diseño, 233 frecuencia, prueba en orden de,
recursos para, 121
hardware
responsabilidades, asignando a
612–613
dependencias, cambio, 98 objetos, 106 gotos reescrito con, 402–403,
mejora del rendimiento con, fuerte cohesión, 105 406–407
591 lista resumida de reglas, 108 pruebas,
sentencias if-then-else, conversión
tiene relaciones, 143 anticipación, 106 enfoque de arriba
a, 447–448
diseño heurístico hacia abajo, 111–112, 113 heurística
puntos clave, 366
abstracciones, formando consistentes, consultar tablas, sustituir,
89–90 algoritmos en comparación con, 12
614–615
alternativas de patrones, 103 evitar múltiples retornos anidados en,
diseño con.Veradivinar error de diseño
fallas, 106–107 consideraciones de
heurístico, 513
392–393
tiempo vinculante, 107 enfoque de
ocultación.Verjerarquías de ocultación de
negativos en, hacer positivo,
abajo hacia arriba para el diseño,
información, beneficios de, 105–106
435–436
112–113 objetivo de diseño de alta fan-in, 80
anidado.Versentencias if anidadas
fuerza bruta, 107 aspectos humanos del software
caso normal primera directriz,
capturar trabajo, 117–118 desarrollo.Vercarácter, 356–357
puntos centrales de control, 107 personal ruta normal primera pauta, 355
cláusulas if nulas, 357
última recargaletveeglrean
rasgado y 899

enunciados simples si-entonces, 355–357 ocultación de información inicializando variables


refactorización, 573 rutinas de acceso para, 340 acumuladores, 243
simplificación, 445–447 diseño de ADTs para, 127 en la pauta de declaración, 241
declaración única, 748–749 tablas, barreras a, 95–96 Ejemplo de C++, 241
reemplazando con, 413–414 tipos de, categorías de secretos, problema de las lista de verificación para, 257

355 94 dependencias circulares, miembros de la clase, 243


declaraciones implícitas, 239–240 95 configuración del compilador, 243

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

428–429 rutinas, 145–146 costos de operaciones, 602


índices, complementando los tipos de datos modificaciones paralelas refactorización consideraciones de división, 293
con, 627–628 indicador, 566 desbordamientos, 293–295
índices, bucle colocación de elementos comunes en rangos de, 294
reformas, 377 árbol, 146 Desarrollo Integrado
lista de control, 389 datos privados vs protegidos, 148 Entornos (IDE), integración
tipos enumerados para, 305 privado, evitando, 143 710–711
valores finales, 377–378 sesgo recomendado en contra, 149 beneficios de, 690–691, 693–694 gran
alcance de, 383–384 rutinas anuladas para hacer explosión, 691
nombres de variables, 265 nada, 146–147 estrategia ascendente, 697–698
bucles infinitos, 367, 374 revisiones clases de instancia única, 146 sub y compilaciones rotas, 703
informales, 467, 492–493 superclases similares, 576 lista de control, 707
900 entrada
aleta de nivel irtsetgtroitpy

integración,continuado pautas para crear, 135–138 práctica de diseño, 111–117


clases, 691, 694, 697 rutinas foráneas, refactorizar con, Programación extrema, 58
continuo, 706 576 importancia de, 850–851
relaciones con el cliente, 694 inconsistencia con los miembros requisitos previos, 28, 33–34 enfoque
compilación diaria y prueba de humo, problema, 138 secuencial comparado,
702–706 abstracción inconsistente, ejemplo 33–34
definido, 689 de, 135–136 componente de pseudocódigo de, 219
desventajas de arriba hacia abajo función de ocultación de información, 93
estrategia, 695–696 integración, especificación durante,
errores, localización, 693 695, 697
j
bucles de interferencia, 617–618
estrategia orientada a características, 700–701 puntos clave para, 160
importancia de los métodos de aproximación, diseño de, 768
Java
ejemplo de aserción en, 190 sintaxis
689–691 mezclas, 149
de expresión booleana, 443
incrementalVerincremental objetos, diseño para, 89
descripción de, 65
integración opuestos, pares de, 137 pobre
especificación de interfaz, 695, 697 ejemplo de abstracción,
excepciones, 198–201
diseño recomendado, 745 ejemplos en
puntos clave, 708 134–135
seguimiento, 693 tiempo real, 247–248 convenciones de
detalles privados en, 139–141
nomenclatura para, 276, 277 ejemplo de
escalonada, 691–692 programático preferido a
parámetros, 176–177 persistencia de
recursos en, 707–708 semántico, 137
variables, 251 recursos para, 159
estrategia orientada al riesgo, 699 rutinas públicas en interfaces
estrategia sándwich, 698–699 preocupación, 141

programación, 694 regla de conveniencia en tiempo de


Javadoc, 807, 815
enfoque de rebanadas, 698 lectura, 141 refactorización, 575–576,
JavaScript, 65
JUnidad, 531
pruebas de humo, 703 579 rutinas, pasar a refactorizar, 575
encuadernación justo a tiempo, 253
estrategias para, resumen, rutinas, sin usar, 576
694 stubs, 694, 696 violaciones semánticas de
resumen de enfoques, 702 encapsulación, 141–142 k
pruebas, 499, 697 información no relacionada, manejo,
decisiones clave de construcción.Ver
estrategia de arriba hacia abajo para, 694– 137 decisiones de construcción
696 integración en forma de T, 701 trabajo interfaces, gráfico.VerInterfaces
estado de datos eliminados, 509–510 tipos
sin superficie, 702 GUI, rutina.Ver también
de proyectos de software, 31–33
enfoque de corte vertical, 696 parámetros de rutinas
integridad, 464 comentando, 808
honestidad intelectual, 826–828 enfoque de rutinas ajenas, refactorización con, L
caja de herramientas intelectual, 20 576 lenguajes, programación.Ver
inteligencia, papel en el carácter, 821 pseudocódigo para, 226 elección del lenguaje de
interfaces, clase variables de miembros públicos, programación Ley de Deméter, 150
aspecto de abstracción de, 89, 576 rutinas, ocultar, 576 diseño
133–138, 566 rutinas, pasar a refactorizar, 575 referencias de matrices, 754
llamadas a clases, refactorización, internacionalización, 48 sentencia de asignación
575 cohesión, 138 interoperabilidad, 48 continuaciones, 758
nivel constante de abstracción, lenguajes interpretados, actuación pares de inicio y fin, 742–743
135–136 de, 600–601 líneas en blanco, 737, 747–748
delegación vs herencia, entrada inválida.Veriteración de estilo de bloque, 738–743
refactorización, 576 validación, código.Ver tambiénbucles estilos de corsé, 734, 740–743 Efectos
documentando, 713, 810 bucles foreach, 367, 372 secundarios de C++, 759–761 lista de
erosión bajo modificación datos iterativos, 255 verificación, 773–774
problema, 138 bucles iteradores, definidos, 367 clases, 768–771
evaluación de la abstracción de, 135 Patrón iterador, 104 declaración estrechamente relacionada

clases de extensión, refactorización concepto de programación estructurada elementos, 755–756


con, 576 de, 456 comentarios, 763–766
formalizando como contratos, 106 iteración en desarrollo expresiones complicadas,
buen ejemplo de abstracción, elección, razones para, 35–36 749–750
133–134 sintonización de código, 850 requisito de consistencia, 735
último leonotprys de nivel superior 901

declaraciones continuas, 754–758 objetivo de diseño de delgadez, 81 eliminando la redundancia de pruebas,


continuaciones de declaraciones de control, avisos legales, 811 610–611
757 longitud de los nombres de las variables, óptimo, frecuencia, prueba en orden de,
estilos de estructura de control, 745– 262 612–613
752 declaraciones, 761–763 niveles de diseño identidades, 630
reglas del discurso, 733 subsistema de lógica de negocios, 85 clases, diseño de, 753
documentación en código, 763–766 divisiones en, 86 subsistema de acceso a evaluación perezosa, tablas de
pares de inicio y fin con doble sangría, base de datos, 85 descripción general de, búsqueda 615–616, sustitución,
746–747 82 614–615
emulando bloques puros, 740–743 paquetes, 82–85 evaluación de cortocircuito, 610
diseño de línea final, 743–745, 751–752 rutinas, 86–87 bucles
extremos de continuaciones, 756–757 sistema de software, 82 anormal, 371
archivos, dentro, 771–773 subsistemas, 82–85 arreglos con, 387–388
Teorema fundamental de subsistema de dependencias del sistema, cuerpos de, procesamiento, 375–376,
Formateo, 732 85 388
gotas, 750–751 subsistema de interfaz de usuario, 85 se recomiendan corchetes, 375
declaraciones incompletas, 754– bibliotecas, código declaraciones de ruptura, 371–372,
755 sangría, 737 propósito de, 717 379–380, 381
interfaces, 768 utilizando la funcionalidad de, 222 lista de control, 388–389
puntos clave, 775 bibliotecas, libro.Versoftware- ajuste de código, 616–624
directrices específicas del idioma, 745 bibliotecas de desarrollo comentando, 804–805
expresiones lógicas, 753 modelos de ciclo de vida pruebas de finalización, ubicación de,
estructura lógica, reflejando, 732, tabla de buenas prácticas para, 368 compuestos, simplificación, 621–
Descargar desde Guau! Libro electrónico <www.wowebook.com>

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

bucles,continuado nombrar, 183, 277–278 medición


código de inicialización para, 373, 374 paréntesis con, 182–183 ventajas de, 677
estructuras de datos iterativas con, 255 revistas sobre programación, argumentando en contra, 678

bucles iteradores, 367, 456 859–860 goles a favor, 679


atasco, 617–618 variables mágicas, evitando, 292, identificación de valores atípicos,
puntos clave, 389 297–298, 308–309 679 recursos para, 679–680
tipo de, generalizado, 367–368 mantenimiento efectos secundarios de, 678

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

punteros en el interior, 620 desarrollo metáforas, software


problemas con, descripción general de, cambio de control.Verconfiguración acrecentar un sistema, 15–16 uso
373 método de pseudocódigo, 385–387 administración algorítmico de, 11, 12 construir
refactorización, 565, 573 actitudes de propiedad del código, 663 metáfora, 16–19 construir vs.
repetir hasta cláusulas, 377 complejidad, 77–79 comprar componentes,
rutinas en, 385 gestión de la configuración.Ver 18
contadores de seguridad con, 378–379 gestión de configuración combinando, 20
alcance de índices, 383–384 pruebas buena codificación, alentando, centrado en la computadora versus centrado en los datos

centinela para, 621–623 tamaño como 662–664 vistas, 11


indicador de refactorización, 565 inspecciones, función de gestión en, personalización, 18
reducción de fuerza, 623–624 486–487 descubrimientos basados en, 9–10 puntos de vista

conmutación, 616 puntos clave, 688 centrados en la tierra vs. centrados en el sol,

terminación, hacer obvio, 377 gerentes, 686 10–11


probar la redundancia, eliminar, medidas, 677–680 ejemplos de, 13–20
610–611 programadores, tratamiento de, agricultura, 14–15
desenrollar, 618–620 680–686 hacer crecer un sistema, 14–15
desconexión, 616–617 estándar de legibilidad, 664 uso heurístico de, 12
pautas variables, 382–384 recursos en, 687 importancia de, 9–11
inicializaciones variables, 249 lista revisar todo el código, 663 premiar desarrollo incremental, 15–16
de verificación de variables, 389 las buenas prácticas, 664 puntos clave para, 21
verificación de terminación, 377 programar, estimar, 671–677 uso de modelado para, 9
bucles while, 368–369 aprobar el código, 663 sobreextensión de, 10
bajo acoplamiento tamaño de los proyectos, efectos de.VerTalla cría de ostras, 15–16
objetivo de diseño, como, 80 estrategias para, de proyectos ejemplo de péndulo, 10
100–102 objetivo de diseño de fan-out de bajo estándares, autoridad para establecer, poder de, 10
a medio, 662 estándares, IEEE, 687, 814 equipos legibilidad, 13
81 de dos personas, 662 méritos relativos de, 10, 11
LSP (principio de sustitución de Liskov), marcadores, defectos de, 787 estructuras simples vs. complejas,
144–145 matrices.Verarreglos 16–17
entornos tecnológicos maduros, tamaño de los proyectos, 19 desechar

67 uno, 13–14 enfoque de caja de


METRO
configuraciones normales máximas, herramientas, 20
Convenciones de nomenclatura de Macintosh,
515 usando, 11–12
275 macro rutinas.Ver tambiénrutinas
ejemplo de recursión de laberinto, 394–396 ejemplo de código de escritura, 13–14
alternativas para, 184
Métrica de complejidad de McCabe, 457, metodologías, 657–659.Ver también
limitaciones en, 184 458 aproximaciones a los métodos de
declaraciones múltiples en, 183
medir dos veces, cortar una vez, 23 desarrollo.Verrutinas
último nivel superiorolbejn
etcrtys 903

reporteros de métricas, 714 puntos clave, 289 rediseño, 453


configuraciones mínimas normales, tipos de información en nombres, simplificación al volver a probar
515 277 condiciones, 445–446
sistemas de misión crítica, 31–32 independencia del idioma simplificación con bloques de ruptura,
entornos de lenguaje mixto, 276 directrices, 272–274 446–447
mixins, 149 longitud, no limitante, 171 resumen de tecnicas para
objetos simulados, 523 Macintosh, 275 reducción, 453–454
modelado, metáforas como.Ver significados en los nombres, demasiado similares, demasiados niveles de, 445–454
metáforas, software 285 bucles anidados
rol moderador en inspecciones, 486 nombres engañosos, 285 diseño, 382–383, 385 ordenar para
modularidad palabras mal escritas, 286 el desempeño, 623 naturaleza no
objetivo de diseño de, 107 consideraciones de lenguaje mixto, determinista del diseño
variables globales, daños por, 276 proceso, 76, 87
337–338 múltiples lenguajes naturales, 287 funciones de lenguaje no estándar, 98
módulos, consideraciones de acoplamiento, números, diferenciando únicamente por, objetos nulos, refactorización, 573
100–102 171 declaraciones nulas, 444–445
herencia múltiple, 148–149 números, 286 números, literales, 292
retornos múltiples de rutinas, opuestos, uso de, 172 tipos de datos numéricos

391–393 parámetros, 178 BC, 297


capacidad de búsqueda de cadenas de archivos múltiples, abreviaturas fónicas, 283 estandarización lista de control, 316

711–712 de prefijos, 279–281 descripciones de advertencias del compilador, 293

procedimientos, 172 beneficio de comparaciones, 440–442


reducción de proliferación, conversiones, mostrando, 293 costos
norte
270 de operaciones, 602 declaraciones,
constantes nombradas.Verconvenciones de
guía de pronunciación, 283 comentarios, 802 tipos de punto
nomenclatura de constantes
propósito de, 270–271 flotante, 295–297,
convención de prefijo "a", 272 legibilidad, 274 316, 602
nombres abreviados, 282–285 relaciones, énfasis de, 271 0 y 1 codificados, 292
pautas de abreviatura, 282 nombres reservados, 287 enteros, 293–295
matrices, 280–281 rutinas, 171–173, 222 números literales, evitando, 292
beneficios de, 270–271
prefijos semánticos, 280–281 nombres números mágicos, evitando, 292
Lenguaje C, 275, 278 cortos, 282–285, 288–289 similitud de magnitudes, muy diferentes,
C++, 275–277 nombres, demasiado, operaciones con, 295
mayúsculas, 274, 286 idiomas que no
285 comparaciones de tipo mixto, 293
distinguen entre mayúsculas y minúsculas,
espaciado de caracteres, 274 t_ desbordamientos, 293–295
273 caracteres, difícil de leer, 287 lista de
convención de prefijos, 272 diccionario rangos de enteros, 294
verificación, 288–289, 780
de sinónimos, utilizando, 283 cero, dividiendo por, 292
variables miembro de clase, 273 clase frente
tipos frente a nombres de variables,
a nombres de objetos, 272–273 operaciones
272–273
comunes, para, 172–173 constantes, 273–
Abreviaturas UDT, 279–280
O
274 objetivos, calidad del software, 466,
variables, para.Vernombres de
beneficios entre proyectos, variables Visual Basic, 278–279 468–469
270 directriz descriptiva, 171 cuándo usar, 271 programación orientada a objetos
documentación, 284–285, sentencias if anidadas ocultando información.Ver
778–780 sentencias de caso, conversión a, ocultación de información

tipos enumerados, 269, 274, 448–449, 451 herencia.Verobjetos de


277–279 convertir a if-then-else herencia.Verclases;
formalidad, grados de, 271
declaraciones, 447–448
polimorfismo de objetos.Ver
expedientes, 811
factorización de rutinas, 449–451
polimorfismo
valores de retorno de función, recursos para, 119, 159
enfoque de método de fábrica,
172 variables globales, 273, 342 acoplamiento objeto-parámetro, 101
convertir a, 452–453
homónimos, 286 objetos
descomposición funcional de,
húngaro, 279 450–451 ADT como, 130
informal, 272–279 enfoque orientado a objetos,
identificación de atributos, 88
parámetros de entrada, 274
convertir a, 452–453
Java, 276, 277
904 fOirbsstetrovpe-rlepvaettleerntry

objetos,continuado definido, 483 pasar parámetros, 333


nombres de clase, diferenciando de, parejas inexpertas, 484 patrones
272–273 puntos clave, 497 ventajas de, 103–104
clases, en contraste con, 86 ritmo, coincidencia, 483 alternativas sugeridas por, 103
contención, identificación, 88 conflictos de personalidad, beneficio de las comunicaciones, 104
eliminación de objetos, 206 484 recursos, 496 reducción de la complejidad con, 103
métodos de fábrica, 103–104, pares giratorios, 483 desventajas de, 105
452–453, 577 jefes de equipo, 484 beneficio de reducción de errores,
identificación, 88 visibilidad del monitor, 103 Método de fábrica, 103–104
herencia, identificación, 88.Ver 484 viendo, 483 recurso para, 120
ademásherencia cuando no usar, 483 tabla de, 104
interfaces, diseño, 89.Ver también parámetros de rutinas primer tema de la gente.Verevaluaciones de
interfaces, clase abstracción y objeto rendimiento de legibilidad, ajuste de
operaciones, identificación, 88 parámetros, 179 rendimiento 487
parámetros, uso como, 179, 574 real, coincidencia con formal, regla de elección de algoritmo, 590
interfaces protegidas, diseño, 180 asteriscos (*) para punteros, requisitos previos de arquitectura, 48
89 334–335 arreglos, 593–594, 603–604 lista de
miembros públicos vs privados, dependencia del comportamiento de, 574 verificación, 607–608
diseño, 89 por referencia vs. por valor, 333 lista de ajuste de código para.Vercomentarios de
mundo real, búsqueda, 87–89 verificación para, 185 ajuste de código, efectos en, 791 dilema
refactorización, 574–576 Orden de la biblioteca C, 175 de objetivos en competencia,
objetos de referencia, 574 comentando, 806–807 595, 605
responsabilidades, asignación a, 106 prefijo const, 176, 177, 274 consideraciones del compilador, 590,
propiedad única, cumplimiento, 151 dependencias, aclaración, 349 596–597
pasos en el diseño, 87–89 documentación, 178 corrección, importancia de,
Patrón de observador, 104 tipos enumerados para, 303 595–596
errores fuera de uno variables de error, 176 indexación de bases de datos, 601

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

32 importancia de, 24 asignaciones de tiempo, 681 Herramientas diferenciales, 712

preparación incompleta, causas de, variaciones en el rendimiento, desmontadores, 720


25–27 681–683 herramientas de edición, 710–713

mezclas iterativas y secuenciales, convenciones de programación herramientas de código ejecutable, 716–720


34–35 elegir, 66 herramientas de perfilado de ejecución, 720
métodos iterativos con, 28, 33–34 lista de verificación de prácticas de fantasyland, 722–723
puntos clave para, 59–60 codificación, 69 reglas de formato.Ver herramientas de diseño gráfico,
tipos de proyectos, 31–33 argumento diseño de programación en lenguajes, 710 grep, 711
lógico a favor, 27 problema de 68–69, 843 IDE, 710–711
ignorancia del gerente, 26 definición elección del lenguaje de programación documentación de interfaz, 713
del problema, 36–38 desarrollo de Ana, 63 puntos clave, 725
requisitos.Ver lenguaje ensamblador, 63 enlazadores, 716

requisitos básico, 65 combinar herramientas, 712

meta de reducción de riesgos, 25 C, 64 reporteros de métricas, 714


habilidades requeridas para el éxito, Do#, 64 búsquedas de cadenas de archivos múltiples,

25 tiempo permitido, 55–56 síndrome C++, 64 711–712


WIMP, 26 Cobol, 64 preprocesadores, 718–719
síndrome de WISCA, 26 expresividad de los conceptos, 63 herramientas específicas del proyecto, 721–
Principio de Proximidad, 242, 351 lenguajes familiares vs. desconocidos, 722 propósito de, 709
datos privados, 148 62 análisis de calidad, 713–714
prerrequisitos de definición de problemas, Fortrán, 64 herramientas de refactorización,
36–38 lenguaje de nivel superior versus lenguaje de nivel inferior 714–715 recursos en, 724
dominio del problema, programación en, productividad, 62 herramientas de reestructuración,

845–847 importancia de, 61–63 715 scripts, 722

desarrollo de habilidades para resolver problemas, Java, 65 verificadores de semántica, 713–714


823 JavaScript, 65 herramientas de código fuente, 710–715
cohesión procesal, 170 Perla, 65 verificadores de sintaxis, 713–714
procedimientos.Ver tambiénrutinas PHP, 65 plantillas, 713
directrices de nomenclatura productividad de, 62 herramientas de prueba, 719

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

practicar, 7 SQL, 65 correspondiente a, 31–33


promedio de la industria, 474 pensamiento, efectos sobre, datos protegidos, 148
tamaño de los proyectos, efectos sobre, 63 Visual Basic, 65 creación de prototipos, 114–115, 468
653 desarrollo profesional, 824–825 herramientas de programación Proximidad, Principio de, 242, 351
organizaciones profesionales, 862 flujo del ensamblador enumerando herramientas, pseudocódigo
programa 720 embellecedores, 712 algoritmos, investigando, 223
control de.Verestructuras de control secuenciales. herramientas de construcción, 716–717 mal, ejemplo de, 218–219 se
Verrequisito previo de organización del programa construcción de la suya propia, 721–722 beneficia de, 219–220
de código de línea recta, Herramientas CASE, 710 cambio, eficiencia de, 220 verificación
45–46 lista de control, 724–725 de errores, 230–231 lista de
tamaño del programaVertamaño de los proyectos generadores de jerarquía de clases, 713 verificación para PPP, 233–234
es y
último top-rlefvaeclto ritnrg 907

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

comentarios de, 220, 791 verificación para, 476

estructura de datos para rutinas, construcción colaborativa.Ver


R
generadores de datos aleatorios,
224 declaraciones de, 226 colaboración
legibilidad 525
definido, 218 corrección, 463
como estándar de gestión, 664
diseño de rutinas, 220–225 consideraciones costos de encontrar defectos, 472
defectos que exponen falta de, 538
sobre el manejo de errores, costos de corregir defectos, 472–473
definidos, 464
222 depuración, papel de, 474–475, 536
formateo para.Verdiseño importancia de,
ejemplo para rutinas, 224 detección de defectos por varios
13, 841–843 beneficio de mantenimiento
funcionalidad de bibliotecas, 222 técnicas, tabla de, 470 aseguramiento
de, 842 variables de nomenclatura para.
bueno, ejemplo de, 219 del proceso de desarrollo
Vernombrando
pautas para un uso efectivo, 218 actividades, 467–468
comentarios de encabezado para rutinas, eficiencia, 464 convenciones; variable nombra

223 directrices de ingeniería, 467


efectos positivos de, 841 programas

comentarios de alto nivel de, actividad explícita para, 466


privados vs. públicos, 842

226–227 auditorías externas, 467


desarrollo profesional,
importancia para, 825
refinamiento iterativo, 219, 225 características externas de,
estructuras, importancia de,
puntos clave para crear, 234 diseño 463–464
de bucle, 385–387 Programación extrema, 471–472 733–734
señal de advertencia, como un,
nombrar rutinas, 222 flexibilidad, 464
849 lectura como habilidad, 824 plan
consideraciones de rendimiento, puertas, 467
de lectura para software
222–223 Principio general del software
desarrolladores, 860–862
PPP.VerPPA Calidad, 474–475
registros, refactorización, 572
requisitos previos, 221 integridad, 464
recursividad
definición de problema, 221 características internas, 464–465
refactorización, 229 puntos clave, 477 alternativas a, 398
revisión, 224–225 mantenibilidad, 464 lista de control, 410

rutinas, pasos en la creación, 217, medición de resultados, 468


definido, 393
factoriales usando, 397–398
223–224 detección de múltiples defectos
números de Fibonacci usando,
prueba, planificación para, 222 Proceso de técnicas recomendadas,
programación de pseudocódigo. 470–471 397–398
directrices para, 394
VerPPA objetivos, establecimiento, 466, 468–
puntos clave, 410
distancia psicológica, 556 conjunto 469 conflictos de optimización, 465–466
ejemplo de laberinto, 394–396
psicológico, 554–555 factores porcentaje de defectos
contadores de seguridad para, 396
psicológicos.Verpersonaje, medición, 469–472
pauta de rutina única, 396 ejemplo
personal portabilidad, 464
de clasificación, 393–394 problemas
miembros de datos públicos, 567 estilo de rendimiento del programador,
de espacio de pila, 397 terminación,
diseño de bloques puros, 738–740 Python basado en objetivos, 468–469
396
creación de prototipos, 468
refactorización
descripción de, 65 legibilidad, 464
problemas de rendimiento, 600 combinación recomendada para, regla 80/20, 582
añadiendo rutinas, 582
473
relaciones de características, algoritmos, 573
q 465–466 arreglos, 572
seguro de calidad.Ver tambiéncalidad de copia de seguridad del código antiguo, 579
fiabilidad, 464
software recursos para, 476 asociaciones de clases bidireccionales,

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

refactorización,continuado reseñas de, 580–581 efectos del proceso de desarrollo sobre,


indicador de cohesión de clase, niveles de riesgo de, 581 40
566 interfaces de clase, 575–576 rutinas, 565–567, 573–574, 578, proyectos de vertido, 41
clases, 566–567, 574–576, 582 errores en, efectos de, 38–39 funcional,
578–579, 582 pautas de seguridad, 579–581, 584 código de lista de verificación, 42 tabla de buenas
ajuste de código, en comparación con, configuración, 568–569 prácticas para, 31–32 importancia de,
609 colecciones, 572 guía de tallas, 580 38–39
comentarios sobre mal código, nivel de declaración, 572–573, punto clave para, 60 no funcional, lista
568 módulos complejos, 583 577–578 de verificación, 42 ajuste del
expresiones condicionales, 573 estrategias para, 582– rendimiento, 589 calidad, lista de
valores constantes que varían entre 584 subclases, 567, 575 verificación, 42–43 tasa de cambio,
subclase, 574 superclases, 575 típica, 563 recursos en desarrollo, 56–
constructores a métodos de fábrica, nivel del sistema, 576–577, 579 57 estabilidad de, 39–40, 840
577 código de eliminación, 568–569
datos de fuentes no controladas, prueba, 580 prueba para, 503
576 listas de tareas para, 580 tiempo permitido para, 55–
conjuntos de datos, relacionados, como herramientas para, 714–715 56 gestión de recursos
indicador, 566 tipos de datos a clases, 572 datos vagabundos, 567 arquitectura para, 47
nivel de datos, 571–572, 577 código feo, interfaces para, 583–584 ejemplo de limpieza, 401–402
defectos, correcciones de, 582 asociaciones de clase unidireccionales, naturaleza restrictiva del diseño, 76
definido, 565 577 herramientas de reestructuración, 715
diseñar código para necesidades futuras, pruebas unitarias para, volviendo a probarVerdeclaraciones de retorno

569–570 580 variables, 571 de prueba de regresión

Principio de no repetirse, advertencias, compilador, lista de control, 410

565 580 referencias (&), C++, 332 cláusulas de protección, 392–393


indicador de código duplicado, 565 pruebas de regresión puntos clave, 410
módulos propensos a errores, 582 herramientas diferenciales múltiples, de una rutina,
expresiones, 571 para, 524 definido, 500 391–393
variables globales, 568 propósito de, 528 legibilidad, 391–392
datos GUI, 576 fiabilidad recursos para, 408
si declaraciones, 573 rutinas cohesivas, 168 reutilización
interfaces, 566, 575–576, 579 definido, 464 definido, 464
puntos clave, 585 actitud religiosa hacia requisitos previos de la arquitectura, 52
lista de pasos planificados, 580 programación papel del revisor en las inspecciones, 486
constantes literales, 571 eclecticismo, 851–852 revisiones
bucles, 565, 573 experimentación en comparación con, lectura de código, 494
activación de mantenimiento, 583 852–853 espectáculos de perros y ponis, 495
clases de intermediarios, 567 efectos nocivos de, 851–853 estilos aspecto educativo de, 482 regla de cada
mal uso de, 582 de diseño que se convierten, 735 línea de código, 663 inspecciones formales,
objetos nulos, 573 gestión de personas, 683–684 en comparación con,
objetos, 574–576 oráculos de software, 851 485
regla uno a la vez, 580 tipos de datos informes.Verrequisitos de formal, calidad de, 467
primitivos sobrecargados, inspecciones formales informal, definido, 467
567 beneficios de, 38–39 proceso de iteración, lugar en, 850
se requieren modificaciones paralelas casos comerciales para, 41 procedimientos de refactorización realizando después,
indicador, 566 control de cambios, 40–41 listas de verificación 580–581
parámetros, 566, 571, 573 paso de para, 40, 42–43 recorridos, 492–493
codificación PPP, 229 miembros codificación sin, 26 desplazamiento a la derecha, 634

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,

prueba booleana, 165 cálculo para diseño interno, 87 568–569


ejemplo de función, en línea, 184–185 parámetros similares, orden de, 176
166–167 parámetro de entrada-modificación-salida similar, refactorización, 574
llamadas, costos de, 601 verificación de orden, 174–175 simple, utilidad de, 166–167 tamaño
errores, 230–231 listas de verificación, 185, declaraciones de interfaz, 226 como indicador de refactorización,
774, 780 clases, conversión a, criterios iteración de pseudocódigo, 225 puntos 565–566
para, clave para, 186, 234 diseño de, 754, pequeño frente a grande, 166,
573 766–768 longitud de, guía para, 173– 173–174 ejemplo de
pasos de limpieza, 232 174 limitaciones, documentación, 808 especificación, 221 código paso a
ajuste de código, 639–640 cohesión lógica, 170 paso, 231 fuerza, 168
codificación a partir de pseudocódigo, beneficio de subclasificación,
225–229 ejemplo de baja calidad, 161–163 165 cohesión temporal, 169
cohesión, 168–171 macro.Vermacro rutinas comprobando desarrollo de prueba primero, 233
cohesión coincidente, 170 mentalmente errores, 230 retornos pruebas, 222, 231, 523 entrada de
comentarios, 805–809, 817 múltiples de, 391–393 parámetros con datos de trampa, 567
cohesión comunicacional, 169 nombre en, 180 sin usar, refactorización, 576
compilación de errores, 230–231 nombrar, 171–173, 222, 277–278, razones válidas para crear,
métrica de complejidad, 458 567 164–167
beneficio de reducción de complejidad, 164 anidado profundamente, 164 nombres de variables, diferenciando
paso de construcción para clases, 217 objetos, pasando a, 179, 574 out de, 272
continuaciones en líneas de llamadas, 756 creación de palabras clave, 175–176 clase incorrecta, indicador de, 566 tiempo
consideraciones de acoplamiento, 100–102 anulable frente a no anulable de ejecución, vinculación durante, 253
estados de datos, 509 rutinas, 145–146
estructuras de datos para, 224 anulado para no hacer nada,
declaraciones, 226 146–147
S
mostradores de seguridad en bucles,
definido, 161 anulando, 156
378–379 integración sándwich, 698–699
guía descriptiva para parámetrosVerparámetros de
andamios
nombrar, 171 rutinas
diseño por contrato, 233 consideraciones de rendimiento, 165,
depurando con, 558
diseño, 86, 220–225 222–223 pruebas, 523–524, 531
escalabilidad, 48.Ver tambiéntamaño de
documentación, 178, 780 beneficio de ocultación de puntero,
proyectos
objetos abatidos, 574 165 beneficio de portabilidad, 165
beneficio de duplicación, 164–165 condiciones posteriores, 221
método científico, pasos clásicos,
disposición final, 767 Lista de verificación de PPP para, 233–234
540
SCM (configuración de software
consideraciones de manejo de errores, condiciones previas, 221

222 requisitos previos, 221


gestión), 665.Ver también
errores en, relación con la longitud de, 173 definición de problema, 221 cohesión
gestión de la configuración
manejadores de eventos, 170 procedimental, 170 guía de nomenclatura de
horarios, estimación.Verestimando
campos de objetos, pasando a, 574 procedimientos, 172 paso de escritura de
horarios
alcance de las variables
archivos, disposición en, 772 pseudocódigo,
223–224 argumento de conveniencia, 250
cohesión funcional, 168–169
definido, 244
funcionalidad de bibliotecas, 222 público, usando en interfaces
preocupación, 141
alcance global, problemas con, 251

consultas, refactorización, 574


910 fsicrrsitbteorpo-llevinelin
es
streyctions

alcance de las variables,continuado tamaño de los proyectos evolución del software


agrupar declaraciones relacionadas, actividades, lista de las de mayor crecimiento, antecedentes para, 563–564 Regla
249–250 655 cardinal de, 565 construcción versus
punto clave, 258 tipos de actividad, efectos sobre, 654–655 mantenimiento,
diferencias de idioma, 244 tiempo en construyendo metáfora para, 19 564
vivo, minimización, 246–248 comunicaciones entre personas, dirección de mejora frente a degradación
localización de referencias a variables, 650 de, 564
245 complejidad, efecto de, 656–657 filosofía de, 564–565
inicializaciones de bucle, 249 defectos creados, efectos sobre, Metáforas de software.Vermetáforas,
argumento de manejabilidad, 251 651–653 software
minimización, pautas para, requisitos de documentación, oráculos de software, 851
249–251 657 calidad del softwareVercalidad de
restringir y expandir táctica, 250 errores de estimación, 656–657 software
rango de variables, 245 requisitos de forma, 657 Técnica primaria del software
asignaciones de valor, 249 puntos clave, 659 Imperativo, 92
nombres de variables, efectos sobre, consideraciones metodológicas, bibliotecas de desarrollo de software
262–263 657–658 bibliografias, 858
papel de escriba en las inspecciones, 486 resumen, 649 construcción, 856
guiones productividad, efectos sobre, 653 revistas, 859–860
herramientas de programación, como, rangos en, 651 resumen, 855, 857–858
722 lentitud de, 600-601 recursos en, 658–659 plan de lectura, 860–862
SDF (desarrollo de software un solo producto, múltiples usuarios, resúmenes de ingeniería de software,
carpetas), 778 656 858
seguridad, 47 programa único, usuario único, 656 pautas de ingeniería de software,
selecciones, código, 455 productos del sistema, 656 467
datos selectivos, 254 sistemas, 656 clasificación, algoritmo recursivo para,
código autodocumentado, 778–781, tamaño de(), 335 393–394
796–797 procesos descuidados, 75–76 código fuente
acoplamiento semántico, 102 punteros inteligentes, 334 aspecto de documentación de, 7
prefijos semánticos, 280–281 correctores pruebas de humo, 703 recurso para, 815
semánticos, 713–714 pruebas centinela metáfora de la acumulación de software, 15–16 herramientas de código fuente

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

concepto de programación estructurada definido, 3–6 diccionarios de datos, 715

de, 454 documentación por código fuente, 7 Herramientas diferenciales, 712

enfoque secuencial, 33–36 naturaleza hecha garantizada de, 7 herramientas de edición, 710–713

cohesión secuencial, 168 importancia de, 6–7 grep, 711


Set() rutinas, 576 puntos clave para, 8 actividades IDE, 710–711
código de configuración, refactorización, 568–569 principales de, 4 por ciento del documentación de la interfaz, 713

herramientas de configuración, 718 desarrollo total herramientas de combinación, 712

evaluación de cortocircuito, 438–440, proceso, 7 reporteros de métricas, 714


610 productividad, importancia en, 7 búsquedas de cadenas de archivos múltiples,

efectos secundarios, C++, 759–761 aprobación del programación como, 5 711–712


código, 663 acoplamiento de parámetro de datos programación contra, 4 herramientas de refactorización, 714–715
simple, 101 acoplamiento de objeto simple, 101 código fuente como documentación, 7 herramientas de reestructuración, 715
tareas en, lista de, 5 verificadores de semántica, 713–714
puntos únicos de control, 308 bloques de diseño de software.Vercarpetas de verificadores de sintaxis, 713–714
declaración única, 748–749 propiedad desarrollo de software de diseño plantillas, 713
singleton, cumplimiento, 104, (SDF), 778 traductores, 715
151 descripción general de la ingeniería de software de herramientas de control de
recursos, 858 versiones, 715 span, 245, 459
Equipo
lasstotfotw es
p-alreevePlro cetrsys 911

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

a abajo depuración, 208–209 problemas de estilo


propósito de, 411–412

directriz, 351–352 mesas de acceso con peldaños, 426–429 problema

Patrón de estrategia, 104 formatoVerdiseño de almacenamiento, 413

llaves transformadoras, 424


objetivo de diseño de estratificación, código autodocumentado, 778–781
Puente de Tacoma Narrows, código
81 strcpy(), 301 aspectos humanos de, 683–684
de eliminación 74, refactorización,
arroyos, 206 subprocedimientos, 161.Ver también
fuerza.Vercohesión rutinas 568–569
Proceso de software de equipo (TSP), 521
912 entrada de nivel ftierastmtsop

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,

construir grupos, 704 diseños, malentendidos, 519 limitaciones 500–502


lista de control, 69 de la vista del desarrollador, 504 rutinas, pruebas de caja negra de,
procesos de desarrollo utilizados por, desarrollo de pruebas, 522 502 andamios, 523–524, 531
840 herramientas diff para, 524 alcance de los defectos, 519
ampliando para cumplir con los horarios, rutinas de controlador, 523 seleccionando casos por conveniencia,
676 gerentes, 686 clases ficticias, 523 516
entorno físico, 684–685 archivos ficticios para, 524 durante la errores de estabilización,
privacidad de las oficinas, 684 construcción, 502–503 facilidad para 542 estándares, IEEE, 532
proceso, importancia para, 839–840 corregir defectos, 519 partición de prueba de base estructurada, 503,
cuestiones religiosas, 683–684 equivalencia, 512 listas de verificación de 505–509
recursos en, 685–686 errores para, 503 objetos trozo, 523
tamaño de los proyectos, efectos de, 650–653 bases de datos de errores, 527 depuradores simbólicos, 526–527
cuestiones de estilo, 683–684 error al adivinar, 513 perturbadores del sistema, 527
asignaciones de tiempo, 681 suposición de presencia de error, 501 prueba del sistema, 500
variaciones en el rendimiento, errores en la prueba misma, 522 tasa de comprobabilidad, 465, 467
681–683 defecto esperada, 521–522 primera o errores de casos de prueba, 522

ondas tecnológicas, determinando su última recomendación, compromiso de tiempo para, 501–502


ubicación en, 66–69 503–504, 531 desarrollo de prueba primero, 233
Patrón de método de plantilla, 104 marcos para, 522, 524 herramientas, lista de, 719
herramientas de plantilla, 713 objetivos de, 501 pruebas unitarias, 499,
cohesión temporal, 169 buenas clases de datos, 515–516 545 casos variados, 545
Descargar desde Guau! Libro electrónico <www.wowebook.com>

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

de cobertura lógica, 506 configuraciones enfoque de caja de herramientas, 20

pruebas automatizadas, 528–529 normales máximas, instrumentos

clases de datos incorrectos, 514–515 515 lista de control, 70

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

522–525, 532 objetos simulados, 523 código fuente.Verherramientas de código fuente

características de, problemático, errores de casos nominales, 515 datos enfoque de arriba hacia abajo para el diseño,

501 antiguos, compatibilidad con, 516 111–113


lista de control, 532 programadores optimistas integración de arriba hacia abajo, 694–696
clases propensas a error, 517–518 limitación, 504 funciones trascendentales, 602, 634
clasificaciones de errores, 518–520 fuera del dominio de la construcción herramientas de traducción, 715
limitación de prueba limpia, 504 defectos, 519 sentencias try-finally, 404–405
errores administrativos (typos), 519 planeando para, 528 integración en forma de T, 701
pruebas de cobertura de código, 506 priorizar la cobertura, 505 conversión de tipos, evitación, 334
pruebas de componentes, 499 probabilidad de corrección, 501, creación de tipos
límites compuestos, 514 defectos 505 C++, 312
de construcción, proporción calidad no afectada por, 501 beneficio de centralización, 314 lista
de, 520–521 generadores de datos aleatorios, de control, 318
cobertura de código, 505–509, 526 pruebas 525 enfoque recomendado para, clases, en comparación con,
de flujo de datos, 509–512 generadores de 503–504 316 ejemplo de, 313–315
datos para, 524–525 herramientas de registro mantenimiento de registros para, directrices para, 315–316 aspecto de
de datos, 526 529–530 pruebas de regresión, 500, ocultación de información de,
depuradores, 526–527 528 requisitos, 503 313–314
depuración, en comparación con, 500 recursos para, 530–531
último topV
-liesvueall eBnatsriyc 913

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

V variables temporales, 267–268 tipos enumerados, 303–306


nombres de tipo, diferenciación de, excepciones en, 198–199, 202
validación
272–273 declaraciones implícitas, apagar,
suposiciones para verificar, lista de, 190
tipos de datos, sospechosos, 188 tipos
visual básico, 279 240
Variables diseño recomendado, 745 convenciones de
enumerados para, 304–305 regla de
tiempo vinculante para, 252–254 nomenclatura para, 278–279 ejemplo de
fuentes de datos externas, 188 regla de
cambio, identificación de áreas de, parámetros, 180
parámetros de entrada, 188 nombres de
98–99 recursos para, 159
variables
lista de verificación para usar, 257–258 estructuras, 320–322
pautas de abreviatura, 282
comentarios para, 803

contadores, 243
914 thpr-oleuvgehlsentry
fwirasltkt-o

W propósito de, 368 problemas perversos, 74–75


recorridos, 492–493, 495–496 señales pruebas, posición de, 369 wikis, 117
de advertencia, 848–850 espacio en blanco síndrome de WIMP, 26
mientras que los bucles
líneas en blanco, 737, 747–748 síndrome de WISCA, 26
ventajas de, 374–375 definidas, 732 soluciones provisionales, documentación, 800

romper declaraciones, 379 agrupando con, 737 escribir metáfora para codificar, 13–14

bucles do-while, 369 importancia de, 736


sangría, 737
salidas adentro, 369–372
declaraciones individuales con,
Z
bucles infinitos, 374
cero, dividiendo por, 292
concepto erróneo de evaluación, 554 753–754
declaraciones nulas con, 444 pruebas de caja blanca, 500, 502
steve mconnell
Steve McConnell es ingeniero jefe de software en Construx Software,
donde supervisa las prácticas de ingeniería de software de Construx.
Steve es el líder del área de conocimientos de construcción del proyecto
Cuerpo de conocimientos de ingeniería de software (SWEBOK). Steve ha
trabajado en proyectos de software en Microsoft, Boeing y otras
empresas del área de Seattle.

Steve es el autor deDesarrollo rápido(1996),Guía de supervivencia de


proyectos de software(1998), yDesarrollo de software profesional
(2004). Sus libros han ganado dos vecesDesarrollo de softwarepremio
Jolt Excellence de la revista al libro de desarrollo de software destacado
del año. Steve también fue el desarrollador principal de SPC Quote
Professional, ganador de un Software Development Pro-
premio a la conductividad En 1998, los lectores deDesarrollo de softwareLa revista nombró a Steve como
una de las tres personas más influyentes en la industria del software, junto con Bill Gates y Linus Torvalds.

Steve obtuvo una licenciatura de Whitman College y una maestría en ingeniería de


software de la Universidad de Seattle. Vive en Bellevue, Washington.

Si tiene algún comentario o pregunta sobre este libro, comuníquese con Steve al
stevemcc@construx.com o víawww.stevemcconnell.com.

También podría gustarte