forked from hectorip/Eloquent-JavaScript-es
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path08_error.txt
More file actions
553 lines (399 loc) · 32.9 KB
/
08_error.txt
File metadata and controls
553 lines (399 loc) · 32.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
:chap_num: 8
:prev_link: 07_elife
:next_link: 09_regexp
:load_files: ["code/chapter/08_error.js"]
= Bugs y manejo de errores =
[chapterquote="true"]
[quote, Brian Kernighan and P.J. Plauger, The Elements of Programming Style]
____
La depuración es dos veces más difícil que escribir código. Por lo tanto, si usted escribe su código lo mejor que puede, por definición, usted no es lo suficientemente inteligente como para depurar lo que escribió.
____
ifdef::interactive_target[]
[chapterquote="true"]
[quote, Master Yuan-Ma, The Book of Programming]
____
Yuan-Ma había escrito un pequeño programa que usaba muchas variables globales y atajos de mala calidad. Al leerlo, un estudiante le preguntó: 'Nos advirtió contra estas técnicas, sin embargo usted las usa en su programa. ¿Cómo puede ser? "El maestro respondió:" No hay necesidad de buscar una manguera de agua si la casa no está en llamas ".
____
endif::interactive_target[]
(((Kernighan+++,+++ Brian)))(((Plaugher+++,+++ P.J.)))(((debugging)))(((error handling)))Un programa es un pensamiento cristalizado. A veces son pensamientos con errores, otras veces los errores se producen al convertir nuestros pensamientos en código. De cualquier manera, el resultado es un programa defectuoso.
(((input)))(((output)))Las fallas en un programa se llaman generalmente _bugs_. Los _bugs_ pueden ser errores de programación o problemas en otros sistemas con los que nuestro programa interactúa. Algunos son inmediatamente evidentes, mientras que otros son sutiles y pueden permanecer ocultos en un sistema durante años.
A menudo, los problemas solamente surgen cuando un programa encuentra una situación que el programador no consideró originalmente. A veces tales situaciones son inevitables. Cuando se le pide al usuario que ingrese su edad (“_your age_”) y este tipea naranja (_“orange”_), esto pone a nuestro programa a prueba. Está situación tiene que ser anticipada y manejada de alguna manera.
== Errores de programación ==
(((parsing)))(((analysis)))Cuando se trata de errores del programador, nuestro tarea es simple: encontrarlos y arreglarlos. Tales errores pueden ir desde simples errores de tipeo, que causan que la computadora nos avice tan pronto como pone los ojos en nuestro programa, a errores sutiles a nuestra comprensión de la forma en que el programa opera, causando resultados incorrectos sólo en situaciones específicas. Los errores de este último tipo pueden tomar semanas para manifestarse.
(((programming language)))(((type)))(((static typing)))(((dynamic
typing)))(((run-time error)))(((error)))El grado en que los lenguajes le ayudan a encontrar tales errores varía. No es sorprendente que JavaScript esté en el extremo de (("apenas ayuda")) de esa escala. Algunos lenguajes quieren saber los tipos de todas sus variables y expresiones antes incluso de ejecutar un programa y le dirán de inmediato cuando un tipo se utiliza de una manera inconsistente. JavaScript considera los tipos sólo cuando realmente ejecuta el programa, e incluso entonces, le permite hacer algunas cosas claramente absurdas sin quejarse, como _x = true * "monkey"_.
(((syntax)))Sí hay algunas cosas que JavaScript no permite hacer. Escribir un programa que no es sintácticamente válido inmediatamente provocará un error. Otras cosas, como llamar a algo que no es una función o buscar una propiedad en un valor indefinido, provocará que se informe un error cuando el programa se está ejecutando y encuentra dicha acción sin sentido.
(((NaN)))(((error)))Pero muchas veces, un cálculo sin sentido simplemente producirá un _NaN_ (no un número) o _undefined_ (valor indefinido). Y el programa continúa feliz, convencido de que está haciendo algo significativo. El error se manifestará sólo más tarde, después de que el valor falso haya recorrido varias funciones. No desencadena un error, pero en silencio hace que el resultado del programa sea incorrecto. Encontrar la fuente de tales problemas puede ser difícil.
(((debugging)))El proceso de encontrar errores (_bugs_) en los programas se llama depuración (_debugging_).
== Modo estricto (_Strict mode_) ==
indexsee:[use strict,strict mode]
(((strict mode)))(((syntax)))(((function)))JavaScript se puede hacer un poco más estricto al permitir el modo estricto. Esto se hace poniendo la cadena "_use strict_" en la parte superior de un archivo o en el cuerpo de función. He aquí un ejemplo:
// test: error "ReferenceError: counter is not defined"
[source,javascript]
----
function canYouSpotTheProblem() {
"use strict";
for (counter = 0; counter < 10; counter++)
console.log("Happy happy");
}
canYouSpotTheProblem();
// → ReferenceError: counter is not defined
----
(((var keyword)))(((variable,global)))Normalmente, cuando te olvidas de poner _var_ delante de tu variable, como _counter_ en el ejemplo, JavaScript crea una variable global en silencio y usa eso. En el modo estricto, sin embargo, un error se informa en su lugar. Esto es muy útil. Debe tenerse en cuenta, sin embargo, que esto no funciona cuando la variable en cuestión ya existe como global, sólo funciona cuando no existe y la asignación que hemos hecho la habría creado.
(((this)))(((global object)))(((undefined)))(((strict mode)))Otro cambio en modo estricto es que _this_ mantiene el valor _undefined_ en funciones que no se llaman como métodos. Al hacer una llamada fuera del modo estricto, _this_ se refiere al objeto de alcance global. Así que si accidentalmente llama a un método o constructor incorrectamente en modo estricto, JavaScript producirá un error tan pronto como intente leer algo de esto, en lugar de trabajar felizmente con el objeto global, crear y leer variables globales.
Por ejemplo, considere el siguiente código, que llama a un _constructor_ sin la palabra clave _new_, de modo que _this_ no se refiere al objeto recien construído:
[source,javascript]
----
function Person(name) { this.name = name; }
var ferdinand = Person("Ferdinand"); // oops
console.log(name);
// → Ferdinand
----
(((error)))Así que la llamada falsa a _Person_ tuvo éxito pero devolvió un valor _undefined_ y creó el nombre de la variable en forma global. En modo estricto, el resultado es diferente.
// test: error "TypeError: Cannot set property 'name' of undefined"
[source,javascript]
----
"use strict";
function Person(name) { this.name = name; }
// Oops, forgot 'new'
var ferdinand = Person("Ferdinand");
// → TypeError: Cannot set property 'name' of undefined
----
Nos dice inmediatamente que algo está mal. Esto es muy útil.
(((parameter)))(((variable,naming)))(((with statement)))El modo estricto (_Strict mode_) hace algunas cosas más: Se prohíbe dar a una función múltiples parámetros con el mismo nombre y elimina completamente ciertas características problemáticas del lenguaje (como la declaración _which_, que es tan equivocada que no volveré a mencionarla en este libro).
(((debugging)))En pocas palabras, poner un _use strict_ en la parte superior de su programa rara vez causa daño y puede ayudarle a detectar un problema.
== _Tests_ ==
(((test suite)))(((run-time error)))Si el lenguaje no va a hacer mucho para ayudarnos a encontrar errores, tendremos que encontrarlos de la manera más difícil: ejecutando el programa y ver si hace lo correcto.
Hacer esto a mano, una y otra vez, es una manera segura de volverse loco. Afortunadamente, a menudo es posible escribir un segundo programa que automatice las pruebas de su programa.
(((Vector type)))Como ejemplo, volvemos a utilizar el tipo _Vector_.
// include_code
[source,javascript]
----
function Vector(x, y) {
this.x = x;
this.y = y;
}
Vector.prototype.plus = function(other) {
return new Vector(this.x + other.x, this.y + other.y);
};
----
Escribiremos un programa para comprobar que nuestra implementación de _Vector_ funciona como se pretende. Luego, cada vez que cambiamos la implementación, seguimos ejecutando el programa de prueba para que podamos estar razonablemente seguros de que no rompimos nada. Cuando agregamos funcionalidad adicional (por ejemplo, un nuevo método) al tipo _Vector_, también agregamos pruebas para la nueva característica.
[source,javascript]
----
function testVector() {
var p1 = new Vector(10, 20);
var p2 = new Vector(-10, 5);
var p3 = p1.plus(p2);
if (p1.x !== 10) return "fail: x property";
if (p1.y !== 20) return "fail: y property";
if (p2.x !== -10) return "fail: negative x property";
if (p3.x !== 0) return "fail: x from plus";
if (p3.y !== 25) return "fail: y from plus";
return "everything ok";
}
console.log(testVector());
// → everything ok
----
(((test suite)))(((testing framework)))(((domain-specific
language)))Escribir pruebas como ésta tiende a producir código bastante repetitivo, incómodo. Afortunadamente, existen piezas de software que ayudan a crear y ejecutar conjuntos de pruebas (_test suites_) proporcionando un lenguaje (en forma de funciones y métodos) adecuado para expresar las pruebas y para emitir información puntual cuando una prueba falla. Estos se llaman _testing frameworks_.
== _Debugging_ ==
(((debugging)))Una vez que usted nota que hay algo mal con su programa porque se comporta mal o produce errores, el siguiente paso es averiguar cuál es el problema.
A veces es obvio. El mensaje de error apuntará en una línea específica de su programa y si observa la descripción del error y esa línea de código, a menudo puede ver el problema.
(((run-time error)))Pero no siempre. A veces la línea que desencadenó el problema es simplemente el primer lugar donde se utiliza de manera no válida un valor erroneo que fue producido en otro lugar. Y a veces no hay ningún mensaje de error en absoluto, sólo un resultado no válido. Si ha estado resolviendo los ejercicios en los capítulos anteriores, es probable que ya haya experimentado tales situaciones.
(((decimal number)))(((binary number)))El programa de ejemplo siguiente intenta convertir un número entero en una cadena en cualquier base (decimal, binario, etc.) seleccionando repetidamente el último dígito y luego dividiendo el número para eliminar este dígito. Pero la valor de retorno ridículo que produce actualmente sugiere que tiene un error (_bug_).
[source,javascript]
----
function numberToString(n, base) {
var result = "", sign = "";
if (n < 0) {
sign = "-";
n = -n;
}
do {
result = String(n % base) + result;
n /= base;
} while (n > 0);
return sign + result;
}
console.log(numberToString(13, 10));
// → 1.5e-3231.3e-3221.3e-3211.3e-3201.3e-3191.3e-3181.3…
----
(((analysis)))Incluso si usted ya noto cual es el problema, finja por un momento que aun no lo a descubierto. Sabemos que nuestro programa está funcionando mal, y queremos averiguar por qué.
(((trial and error)))Aquí es donde debe resistir el impulso de comenzar a hacer cambios aleatorios en el código. Trate de analizar lo que está sucediendo y llegar a una teoría de por qué podría estar sucediendo. Luego, haga observaciones adicionales para probar esta teoría o, si aún no tiene una teoría, haga observaciones adicionales que podrían ayudarle a encontrar una.
(((console.log)))(((output)))(((debugging)))(((logging)))Poner unas pocas llamadas _console.log_ estratégicas en el programa es una buena manera de obtener información adicional sobre lo que está haciendo el programa. En este caso, queremos que _n_ tome los valores _13_, _1_ y luego _0_. Vamos a escribir su valor al inicio del bucle.
----
13
1.3
0.13
0.013
…
1.5e-323
----
(((rounding)))_Right_. Dividir _13_ por _10_ no produce un número entero. En lugar de _n / = base_, lo que realmente queremos es _n = Math.floor (n / base)_ para que el número se desplace correctamente a la derecha.
(((JavaScript console)))(((breakpoint)))(((debugger statement)))Una alternativa al uso de _console.log_ es utilizar las capacidades de depuración de su navegador. Los navegadores modernos vienen con la capacidad de establecer un ((punto de interrupción)) en una línea específica de su código. Esto hará que la ejecución del programa se detenga cada vez que se alcanza la línea con el punto de interrupción y le permite inspeccionar los valores de las variables en ese punto. No entraré en detalles aquí ya que los depuradores difieren de navegador a navegador, pero busque en las herramientas de desarrollo de su navegador y busque en la Web más información. Otra forma de establecer un punto de interrupción es incluir una declaración de depurador (que consiste simplemente en la palabra clave _debugger_) en su programa. Si las herramientas de desarrollo de su navegador están activas, el programa se detendrá cada vez que llegue a esa declaración, y podrá inspeccionar su estado.
== Propagación de errores ==
(((input)))(((output)))(((run-time
error)))(((error)))(((validation)))No todos los problemas pueden ser prevenidos por el programador. Si su programa se comunica con el mundo exterior de alguna manera, existe la posibilidad de que la entrada que obtenga sea inválida o que otros sistemas con los que intente hablar estén rotos o inaccesibles.
(((error recovery)))Los programas sencillos, o programas que se ejecutan sólo bajo su supervisión, pueden permitirse el lujo de dejar de funcionar cuando se produce un problema de este tipo. Examinarás el problema y lo intentarás de nuevo. Pero las aplicaciones "reales" no se pueden simplemente desconectar. A veces lo correcto es tomar la entrada mala y seguir corriendo. En otros casos, es mejor informar al usuario de lo que salió mal y luego desconectar. Pero en cualquier situación, el programa tiene que hacer algo activamente en respuesta al problema.
(((promptInteger function)))(((validation)))Digamos que tiene una función _promptInteger_ que pide al usuario un número entero y lo devuelve.
Pero ¿Qué debe devolver si el usuario ingresa una palabra, por ejemplo confunde "_your age_" por “_orange_”?
(((null)))(((undefined)))(((return value)))(((special return
value)))Una opción es hacer que devuelva un valor especial. Las opciones comunes para estos valores son _null_ y no _undefined_.
// test: no
[source,javascript]
----
function promptNumber(question) {
var result = Number(prompt(question, ""));
if (isNaN(result)) return null;
else return result;
}
console.log(promptNumber("How many trees do you see?"));
----
Esta es una buena estrategia. Ahora cualquier código que llama a _promptNumber_ debe comprobar si se ha leído un número real y, de no ser así, debe reiniciar el proceso de alguna manera, tal vez preguntando de nuevo o rellenando con un valor predeterminado. O podría devolver un valor especial a su llamador (_caller_) para indicar que no pudo hacer lo que se le pidió.
(((error handling)))En muchas situaciones, sobre todo cuando los errores son comunes y el metodo _caller_ debe tenerlo explícitamente en cuenta, devolver un valor especial es una manera prolija de indicar un error. Sin embargo, tiene sus desventajas. Primero, ¿qué pasa si la función ya puede devolver todo tipo posible de valores? Para esta función, es difícil encontrar un valor especial que pueda distinguirse de un resultado válido.
(((special return value)))(((readability)))La segunda cuestión con la devolución de valores especiales es que puede conducir a algún código muy abarrotado. Si un pedazo de código llama a _promptNumber_ 10 veces, tiene que comprobar 10 veces si se devolvió _null_. Y si su respuesta a encontrar _null_ es simplemente devolver _null_ a sí mismo, el llamador (_caller_) a su vez tendrá que comprobarlo, y así sucesivamente.
== Excepciones ==
(((error handling)))Cuando una función no puede proceder normalmente, lo que nos gustaría hacer es simplemente detener lo que estamos haciendo y trabajar en un contexto donde sabemos cómo manejar el problema. Esto es el “manejo de excepciones”.
(((control flow)))(((raising (exception))))(((throw keyword)))(((call
stack)))Las excepciones (_exceptions_) son un mecanismo que hace posible que el código que se ejecuta en un problema lance (_throw_) una excepción, que es simplemente lanzar un valor. Elevar una excepción se asemeja en cierta medida a un retorno súper cargado de una función: se eleva no sólo de la función actual, sino también de sus llamantes, hasta la primera llamada que inició la ejecución actual. Esto se llama desenrollar la pila (_unwinding the stack_). Recuerde el concepto de “pila de llamadas de función” en el link:03_functions.html#stack[Capítulo 3]. Una excepción reduce el zoom de esta pila, eliminando todos los contextos de llamada que encuentra.
(((error handling)))(((syntax)))(((catch keyword)))Si las excepciones siempre estan cerca de la parte inferior de la pila, no serían de mucha utilidad, pues sólo proporcionaría una nueva manera de lanzar su programa. Su poder reside en el hecho de que usted puede establecer "obstáculos" a lo largo de la pila para capturar la excepción. Entonces usted puede actuar, después de que el programa continúa funcionando en el punto donde registró la excepción.
He aquí un ejemplo:
[[look]]
[source,javascript]
----
function promptDirection(question) {
var result = prompt(question, "");
if (result.toLowerCase() == "left") return "L";
if (result.toLowerCase() == "right") return "R";
throw new Error("Invalid direction: " + result);
}
function look() {
if (promptDirection("Which way?") == "L")
return "a house";
else
return "two angry bears";
}
try {
console.log("You see", look());
} catch (error) {
console.log("Something went wrong: " + error);
}
----
(((exception handling)))(((block)))(((throw keyword)))(((try
keyword)))(((catch keyword)))La palabra clave _throw_ se utiliza para generar una excepción. Capturar una se hace envolviendo (_wrapping_) un pedazo de código en un bloque _try_, seguido por la palabra clave _catch_. Cuando el código del bloque _try_ genera una excepción, se evalúa el bloque _catch_. El nombre de la variable (entre paréntesis) después de la captura se enlazará al valor de la excepción. Una vez finalizado el bloqueo del bloque (o si el bloque _try_ termina sin problemas), el control se desarrolla debajo de toda la sentencia _try_/_catch_.
(((debugging)))(((call stack)))(((Error type)))(((stack
trace)))En este caso, utilizamos el constructor _Error_ para crear nuestro valor de excepción (_new Error_). . Se trata de un constructor de JavaScript estándar que crea un objeto con una propiedad de mensaje. En los entornos de JavaScript modernos, las instancias de este constructor también recopilan información sobre la pila de llamadas que existía cuando se creó la excepción, un denominado seguimiento de la pila (_stack trace_). Esta información se almacena en la propiedad de la pila y puede ser útil al intentar depurar un problema: nos indica la función exacta donde ocurrió el problema y cuáles otras funciones llevaron a la llamada que falló.
(((exception handling)))Tenga en cuenta que en nuestro ejemplo la función _look_ ignora por completo la posibilidad de que _promptDirection_ pueda salir mal. Esta es la gran ventaja de las excepciones: el código de manejo de errores sólo es necesario en el punto donde se produce el error y en el punto donde se maneja. Las funciones intermedias pueden desentenderse de todo.
Bueno, casi...
== Limpieza después de excepciones ==
(((exception handling)))(((cleaning up)))(((withContext
function)))(((dynamic scope)))Considere la siguiente situación: una función, _withContext_, quiere asegurarse de que, durante su ejecución, la variable de nivel superior _context_ contiene un valor específico. Después de que termina, restaura esta variable a su valor anterior.
// include_code
[source,javascript]
----
var context = null;
function withContext(newContext, body) {
var oldContext = context;
context = newContext;
var result = body();
context = oldContext;
return result;
}
----
¿Qué pasa si _body_ plantea una excepción? En ese caso, la llamada a _withContext_ será eliminada de la pila por la excepción, y _context_ nunca volverá a su valor anterior.
(((block)))(((try keyword)))(((finally keyword)))Hay una característica más que tiene _try_. Puede ser seguida por un bloque _finally_ en lugar de, o además de, un bloque _catch_. Un bloque _finally_ dice "No importa lo que pase, ejecute este código después de intentar ejecutar el código en el bloque _try_". Si una función tiene que limpiar algo, el código de limpieza normalmente se debe poner en el bloque _finally_.
// include_code
[source,javascript]
----
function withContext(newContext, body) {
var oldContext = context;
context = newContext;
try {
return body();
} finally {
context = oldContext;
}
}
----
(((withContext function)))Tenga en cuenta que ya no tenemos que almacenar el resultado del _body_ (que queremos devolver) en una variable. Incluso si regresamos directamente desde el bloque _try_, se ejecutará el bloque _finally_. Podemos hacerlo y estar seguros:
// test: no
[source,javascript]
----
try {
withContext(5, function() {
if (context < 10)
throw new Error("Not enough context!");
});
} catch (e) {
console.log("Ignoring: " + e);
}
// → Ignoring: Error: Not enough context!
console.log(context);
// → null
----
A pesar de que la función llamada por withContext explotó, en _withContext_ se limpió adecuadamente la variable _context_.
== Selective catching ==
(((uncaught exception)))(((exception handling)))(((JavaScript
console)))(((developer tools)))(((call stack)))(((error)))Cuando una excepción hace todo el camino a la parte inferior de la pila sin ser capturada, se maneja por el medio ambiente. Lo que esto significa difiere entre entornos. En los navegadores, normalmente se escribe una descripción del error en la consola de JavaScript (accesible a través del menú Herramientas o Desarrollador del navegador).
(((crash)))(((error handling)))Para los errores o problemas del programador que el programa no puede manejar, simplemente dejar pasar el error es lo correcto a menudo. Una excepción no tratada es una forma razonable de señalar un programa roto, y la consola de JavaScript, en navegadores modernos, le proporcionará cierta información sobre qué llamadas de función estaban en la pila cuando ocurrió el problema.
(((user interface)))Para los problemas que se espera que sucedan durante el uso rutinario, estrellarse con una excepción no tratada no es una respuesta amigable.
(((syntax)))(((function,application)))(((exception handling)))(((Error
type)))Los usos no válidos del lenguaje, como hacer referencia a una variable inexistente, buscar una propiedad en _null_ o llamar a algo que no es una función, también darán lugar a que se generen excepciones. Tales excepciones se pueden capturar igual que sus propias excepciones.
(((catch keyword)))Cuando se introduce un _catch_, todo lo que sabemos es que algo en nuestro _try_ causó una excepción. Pero no sabemos qué, ni qué excepción causó.
(((exception handling)))JavaScript (en una omisión bastante flagrante) no proporciona soporte directo para capturar selectivamente las excepciones: o bien las captura todas o no las captura. Esto hace que sea muy fácil asumir que la excepción que se obtiene es la misma que la que se estaba pensando cuando escribió el bloque _catch_.
(((promptDirection function)))Pero puede que no lo sea. Es posible que se haya violado alguna otra suposición o que haya introducido un error en algún lugar que esté causando una excepción. He aquí un ejemplo, que intenta seguir llamando a _promptDirection_ hasta obtener una respuesta válida:
// test: no
[source,javascript]
----
for (;;) {
try {
var dir = promtDirection("Where?"); // ← typo!
console.log("You chose ", dir);
break;
} catch (e) {
console.log("Not a valid direction. Try again.");
}
}
----
(((infinite loop)))(((for loop)))(((catch keyword)))(((debugging)))El constructo _for (;;)_ es una forma de crear intencionalmente un bucle que no termina por sí solo. Salimos del bucle sólo cuando se da una dirección válida. Pero hemos escrito erróneamente promptDirection, lo que resultará en un error de "variable indefinida". Debido a que el bloque de _catch_ ignora completamente su valor de excepción (_e_), suponiendo que sabe cuál es el problema, trata erróneamente el error de variable como indicativo de entrada incorrecta. Esto no sólo causa un bucle infinito, sino que también "entierra" el útil mensaje de error sobre la variable mal escrita.
AComo regla general, no cubra las excepciones de captura a menos que sea con el propósito de "enrutarlas" en alguna parte, por ejemplo, a través de la red para decirle a otro sistema que nuestro programa se ha estrellado. E incluso entonces, piense cuidadosamente en cómo podría estar ocultando información.
(((exception handling)))Así que queremos capturar un tipo específico de excepción. Podemos hacer esto comprobando en el bloque _catch_ si la excepción que conseguimos es la que buscamos y probar de otra manera. Pero, ¿cómo reconocemos una excepción?
Por supuesto, podríamos igualar su propiedad de mensaje con el mensaje de error que esperamos. Pero esa es una manera inestable de escribir código: usariamos la información destinada al consumo humano (el mensaje) para tomar una decisión programática. Tan pronto como alguien cambia (o traduce) el mensaje, el código dejará de funcionar.
(((Error type)))(((instanceof operator)))Más bien, definamos un nuevo tipo de error y usamos _instanceof_ para identificarlo.
// include_code
[source,javascript]
----
function InputError(message) {
this.message = message;
this.stack = (new Error()).stack;
}
InputError.prototype = Object.create(Error.prototype);
InputError.prototype.name = "InputError";
----
(((throw keyword)))(((inheritance)))El prototipo se crea al derivar de _Error.prototype_ para que _instanceof Error_ también devuelva _true_ para los objetos _InputError_. También se le da una propiedad de nombre (_name_) ya que los tipos de error estándar (_Error_, _SyntaxError_, _ReferenceError_, etc.) también tienen dicha propiedad.
(((call stack)))La asignación a la propiedad _stack_ intenta dar a este objeto un rastreo de pila algo útil (en plataformas que lo soportan) creando un objeto de error regular y luego utilizando la propiedad _stack_ de ese objeto como propio.
(((promptDirection function)))Ahora _promptDirection_ puede lanzar tal error.
// include_code
[source,javascript]
----
function promptDirection(question) {
var result = prompt(question, "");
if (result.toLowerCase() == "left") return "L";
if (result.toLowerCase() == "right") return "R";
throw new InputError("Invalid direction: " + result);
}
----
(((exception handling)))Y el bucle _for_ puede atraparlo con más cuidado.
// test: no
[source,javascript]
----
for (;;) {
try {
var dir = promptDirection("Where?");
console.log("You chose ", dir);
break;
} catch (e) {
if (e instanceof InputError)
console.log("Not a valid direction. Try again.");
else
throw e;
}
}
----
(((debugging)))Esto captura sólo instancias de _InputError_ y permite excepciones a través de _throw_. Si reintroduce el error de tipeo, el error de variable _undefined_ se informará correctamente.
== Assertions ==
(((assert function)))(((assertion)))(((debugging)))Las afirmaciones son una herramienta para comprobar la salud básica de los errores de programación. Considere la función auxiliar _assert_:
[source,javascript]
----
function AssertionFailed(message) {
this.message = message;
}
AssertionFailed.prototype = Object.create(Error.prototype);
function assert(test, message) {
if (!test)
throw new AssertionFailed(message);
}
function lastElement(array) {
assert(array.length > 0, "empty array in lastElement");
return array[array.length - 1];
}
----
(((validation)))(((run-time
error)))(((crash)))(((assumption)))(((array)))Esto proporciona una manera compacta de cumplir las expectativas, ayudando a alertar del programa si la condición declarada no se cumple. Por ejemplo, la función _lastElement_, que recupera el último elemento de una matriz, retornaría indefinidamente arrays vacíos si la aserción (_assertion_) se omitiera. Obtener el último elemento de una matriz vacía no tiene mucho sentido, por lo que es casi seguro que es el resultado de un error de programador.
(((assertion)))(((debugging)))Las afirmaciones (_Assertions_) son una manera de asegurarse de que los errores causan fallas en el punto del error, en lugar de producir silenciosamente valores sin sentido que pueden causar problemas en una parte no relacionada del sistema.
== Resumén ==
Los errores y malas aportaciones son hechos de la vida. Los errores en los programas necesitan ser encontrados y arreglados. Pueden ser más fáciles de notar al tener suites de pruebas (_test suites_) automatizadas y añadir aserciones a sus programas.
Los problemas causados por factores ajenos al control del programa normalmente deben manejarse con gracia. A veces, cuando el problema se puede manejar localmente, los valores de retorno especiales son una forma sana de rastrearlos. De lo contrario, las excepciones son preferibles.
El lanzamiento de una excepción provoca que la pila de llamadas se desenrolle hasta el siguiente bloque de _try_/_catch_ o hasta la parte inferior de la pila. El valor de excepción se dará al bloque de _catch_ que lo captura, lo que debería comprobar que es realmente el tipo esperado de excepción y luego hacer algo con él. Para manejar el flujo de control impredecible causado por las excepciones, finalmente los bloques se pueden usar para asegurar que un pedazo de código siempre se ejecuta al finalizar un bloque.
== Ejercicios ==
=== Retry ===
(((primitiveMultiply (exercise))))(((exception handling)))(((throw
keyword)))Digamos que tiene una función primitiveMultiply que, en el 50 por ciento de los casos, multiplica dos números, y en el otro 50 por ciento, plantea una excepción del tipo MultiplicatorUnitFailure. Escribir una función que envuelva esta mala función y sigue intentandolo hasta que una llamada tenga éxito, después de lo cual devuelva el resultado.
(((catch keyword)))Asegúrese de manejar solo las excepciones que desea manejar.
ifdef::interactive_target[]
// test: no
[source,javascript]
----
function MultiplicatorUnitFailure() {}
function primitiveMultiply(a, b) {
if (Math.random() < 0.5)
return a * b;
else
throw new MultiplicatorUnitFailure();
}
function reliableMultiply(a, b) {
// Your code here.
}
console.log(reliableMultiply(8, 8));
// → 64
----
endif::interactive_target[]
!!pista!!
(((primitiveMultiply (exercise))))(((try keyword)))(((catch
keyword)))(((throw keyword)))La llamada a _primitiveMultiply_ obviamente ocurre en un bloque _try_. El bloque _catch_ correspondiente debe volver a crear la excepción cuando no es una instancia de _MultiplicatorUnitFailure_ y asegurarse de que la llamada se reintente cuando si lo es.
Para hacer el reintento, puede utilizar un bucle que se rompe sólo cuando una llamada tiene éxito -como en el ejemplo de vista anterior en este capítulo- o recursión de uso (_use recursion_) y espero que no obtenga una cadena de errores tan larga que se desborda la pila (Lo qué seguro ocurrirá).
!!pista!!
=== La caja cerrada ===
(((locked box (exercise))))Considere el siguiente (y bastante artificial) objeto:
object:
// include_code
[source,javascript]
----
var box = {
locked: true,
unlock: function() { this.locked = false; },
lock: function() { this.locked = true; },
_content: [],
get content() {
if (this.locked) throw new Error("Locked!");
return this.cContent;
}
};
----
(((private property)))(((access control)))It is a ((box)) Es una caja con una cerradura. Dentro hay una matriz, pero sólo se puede obtener cuando se desbloquea la caja. No se permite acceder directamente a la propiedad _cContent_.
(((finally keyword)))(((exception handling)))Escriba una función llamada _WithBoxUnlocked_ que toma un valor de función como argumento, desbloquea la casilla, ejecuta la función y, a continuación, asegura que la casilla se bloquea de nuevo antes de volver, independientemente de si la función de argumento ha devuelto normalmente o ha lanzado una excepción.
ifdef::interactive_target[]
[source,javascript]
----
function withBoxUnlocked(body) {
// Your code here.
}
withBoxUnlocked(function() {
box.content.push("gold piece");
});
try {
withBoxUnlocked(function() {
throw new Error("Pirates on the horizon! Abort!");
});
} catch (e) {
console.log("Error raised:", e);
}
console.log(box.locked);
// → true
----
Para obtener puntos adicionales, asegúrese de que si llama a WithBoxUnlocked cuando la caja ya está desbloqueada, la caja permanezca desbloqueada.
endif::interactive_target[]
!!pista!!
(((locked box (exercise))))(((finally keyword)))(((try keyword)))Este ejercicio requiere un bloque _finally_, como usted probablemente adivinó. Su función primero debe desbloquear la caja y luego llamar a la función de argumento desde dentro de un _try_. El bloque _finally_ después debe bloquear la caja de nuevo.
Para cerciorarse de que no bloqueamos la caja cuando todavía no estaba bloqueada, revisar su bloqueo al inicio de la función y desbloquearla y bloquearla sólo cuando empezó bloqueada.
!!pista!!