flowchart TD
A([Entrem al try]) --> B["Executem el codi"]
B --> C{"Hi ha<br/>excepció?"}
C -->|No| D["Saltem el catch"]
C -->|Sí| E["Aturem el try<br/>i saltem al catch"]
E --> F["Executem el catch"]
D --> G([Continuem])
F --> G
8 Excepcions i depuració
Objectius
- Distingir entre errors de compilació, errors d’execució i errors lògics.
- Entendre què és una excepció i com Java l’utilitza per assenyalar problemes.
- Capturar excepcions amb
try-catch-finally. - Diferenciar excepcions comprovades (checked) i no comprovades (unchecked).
- Llançar les teves pròpies excepcions amb
throw. - Fer servir assercions per validar suposicions durant el desenvolupament.
- Depurar programes amb el debugger: punts de ruptura, execució pas a pas i inspecció de variables.
- Documentar el teu codi amb Javadoc.
Fins ara hem suposat que tot anava bé: l’usuari introduïa nombres quan li demanàvem nombres, els fitxers existien, les divisions no eren per zero… El món real no és tan amable. Els programes robustos han de preveure allò que pot anar malament i reaccionar-hi amb elegància. Aprendrem a gestionar errors amb excepcions i a caçar-los amb les eines de depuració.
8.1 Tipus d’errors
No tots els errors són iguals. Segons quan apareixen, els classifiquem en tres famílies:
| Tipus d’error | Quan apareix? | Qui l’avisa? | Exemple |
|---|---|---|---|
| De compilació | Al compilar, abans d’executar | El compilador | Un nom mal escrit, bblidar un ; |
| D’execució | Mentre el programa s’executa | La JVM (una excepció) | Dividir per zero, fitxer inexistent |
| Lògic | Mai avisa: el programa “funciona” però dona un resultat incorrecte | Ningú (l’has de trobar tu) | Sumar quan calia restar |
Els errors de compilació són els més amables: el codi no compila i no es pot executar. Els errors d’execució apareixen quan menys t’ho esperes i, si no els gestiones, aturen el programa de cop. Els errors lògics són els més perillosos: el programa sembla que va bé, però els resultats no són correctes.
- Error de compilació: la recepta té una frase sense sentit i no la pots ni llegir. O està en un idioma que no entenem.
- Error d’execució: la recepta demana un ingredient que no tens a la nevera i t’atures a mig fer.
- Error lògic: has seguit la recepta al peu de la lletra, però has confós el sucre amb la sal. El plat surt… però no s’hi val.
8.2 Què és una excepció?
Una excepció és un objecte que Java crea automàticament quan passa alguna cosa anòmala durant l’execució.
Quan es produeix, diem que Java llança (throws) l’excepció.
Si ningú la captura (catch), el programa s’atura i mostra un missatge d’error anomenat traça de pila (stack trace).
Exemple. Aquest codi divideix per zero. Fitxer Prova.java.
int a = 10;
int b = 0;
int resultat = a / b; // ArithmeticException!
System.out.println(resultat);En executar-lo, el programa s’atura i escriu una cosa semblant a:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Prova.main(Prova.java:4)
Aquest missatge et diu tres coses valuoses:
- Quin tipus d’error és (
ArithmeticException), - Què ha passat (
/ by zero) - On (fitxer
Prova.java, línia 4). Aprendre a llegir la traça de pila és una de les habilitats més útils que desenvoluparàs.
Algunes excepcions habituals que et trobaràs aviat:
| Excepció | Causa típica |
|---|---|
ArithmeticException |
dividir un enter per zero |
NullPointerException |
usar una variable que val null |
ArrayIndexOutOfBoundsException |
accedir a una posició inexistent d’un array (Capítol 9) |
NumberFormatException |
convertir a nombre un text que no ho és ("hola") |
InputMismatchException |
l’usuari escriu text quan Scanner esperava un nombre |
8.3 Capturar excepcions amb try-catch
Per evitar que una excepció aturi el programa, l’envoltem amb un bloc try-catch.
La idea és: “intenta (try) fer això; i si falla, captura’l (catch) i reacciona”.
try {
// Codi que pot fallar.
} catch (TipusExcepcio e) {
// Què fem si falla.
}
Exemple: demanar un nombre sense que el programa falli si l’usuari escriu lletres:
Scanner sc = new Scanner(System.in);
try {
System.out.print("Introdueix un nombre: ");
int n = sc.nextInt();
System.out.println("El doble és " + (n * 2));
} catch (InputMismatchException e) {
System.out.println("Això no era un nombre!");
}Si l’usuari escriu 42, tot va bé. Si escriu hola, en comptes d’aturar-se, el programa entra al catch i mostra un missatge amigable.
Aquest és el flux que segueix Java:
Capturar diferents excepcions
Un mateix try pot tenir diversos catch per reaccionar de manera diferent segons el tipus d’error:
try {
int[] notes = {5, 7, 9};
int posicio = sc.nextInt();
System.out.println(100 / notes[posicio]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Posició fora de l'array!");
} catch (ArithmeticException e) {
System.out.println("No es pot dividir per zero!");
}Exception per a tot
És temptador escriure catch (Exception e) per capturar-ho tot d’una sola vegada. Funciona, però amaga informació: no saps quin error ha passat i pots enmascarar bugs greus. Captura sempre el tipus més concret que esperes. Deixa Exception només com a última xarxa de seguretat.
8.4 Bloc finally
A vegades hi ha codi que s’ha d’executar tant si hi ha error com si no: tancar un fitxer, alliberar una connexió… per a això existeix el bloc finally, que s’executa sempre, al final:
try {
// Codi que pot fallar
} catch (Exception e) {
// Gestió de l'error
} finally {
// S'executa SEMPRE, hi hagi hagut error o no
System.out.println("Neteja feta.");
}El finally és com apagar el llum en sortir d’una habitació: tant si has fet la feina com si t’ha interromput una trucada, en sortir apagues el llum igualment.
8.5 Excepcions comprovades i no comprovades
Java divideix les excepcions en dues categories:
- No comprovades (unchecked): hereten de
RuntimeException. Solen ser errors de programació (dividir per zero,null…). El compilador no t’obliga a gestionar-les. - Comprovades (checked): la resta (com
IOException). Representen problemes externs previsibles (un fitxer que no existeix). El compilador t’obliga a gestionar-les: o les captures ambtry-catch, o declares que el mètode les pot llançar ambthrows.
Aquesta és la jerarquia simplificada de classes d’excepcions a Java:
%%{init: {"class": {"nodeSpacing": 100, "rankSpacing": 80}}}%%
classDiagram
direction TB
Throwable <|-- Error
Throwable <|-- Exception
Exception <|-- IOException
Exception <|-- RuntimeException
class Throwable throwableCls
class Error errorCls
class Error {
<<No es capturen>>
}
class Exception checkedCls
class Exception {
<<checked>>
}
class IOException checkedCls
class RuntimeException uncheckedCls
class RuntimeException {
<<unchecked>>
}
Error vs Exception
Al capdamunt de tot hi ha Throwable, del qual pengen Error i Exception. Els Error (com quedar-se sense memòria) són problemes tan greus que normalment no s’han d’intentar capturar. Tu treballaràs gairebé sempre amb Exception i les seves filles.
8.6 Llançar excepcions amb throw
Java llença excepcions quan detecta una anomalia, però com a programador tu també pots fer-ho quan detectes una situació que el teu codi considera invàlida.
Es fa amb la paraula clau throw:
public static void comprovarEdat(int edat) {
if (edat < 0) {
throw new IllegalArgumentException("L'edat no pot ser negativa: " + edat);
}
System.out.println("Edat vàlida: " + edat);
}Si algú crida comprovarEdat(-5), es llança una IllegalArgumentException amb el nostre missatge.
Així obliguem qui utilitza el nostre mètode a passar-nos dades correctes.
throw amb throws
throw(sense s): llança una excepció ara mateix, dins d’un mètode.throws(amb s): declara a la capçalera d’un mètode que aquest pot llançar una excepció comprovada, per avisar-ne qui el crida.
public static void llegirFitxer(String nom) throws IOException {
// ... codi que pot llançar IOException
}Escriu un mètode arrelQuadrada(double x) que llanci una IllegalArgumentException si x és negatiu, i que retorni Math.sqrt(x) en cas contrari.
8.7 Assercions
Una asserció és una comprovació que fem durant el desenvolupament per verificar que una suposició nostra és certa.
Es fa amb la paraula clau assert:
int edat = calcularEdat();
assert edat >= 0 : "L'edat mai hauria de ser negativa aquí";Si la condició és falsa, es llança un AssertionError amb el missatge.
Les assercions serveixen per caçar errors de programació nostra, no per validar dades de l’usuari.
A Java, les assercions no s’executen si no les actives explícitament amb l’opció -ea (enable assertions) en executar el programa.
Mai les has de fer servir per validar dades reals de l’usuari (per a això, if + throw). Fes-les servir només com a ajuda de desenvolupament.
8.8 Depuració amb el debugger
Quan un programa no fa el que esperes, tens dues opcions.
- La ràpida però pobra és omplir el codi de
System.out.printlnper veure valors. - La professional és fer servir el debugger (depurador), una eina integrada a IntelliJ IDEA i VS Code que et permet aturar el programa i mirar-hi a dins.
Les tres eines fonamentals del debugger:
- Punt de ruptura (breakpoint). Fas clic al marge esquerre, al costat d’una línia. El programa s’aturarà just abans d’executar-la.
- Execució pas a pas: un cop aturat, avances instrucció per instrucció.
- Step Over. Executa la línia actual i passa a la següent.
- Step Into. Si la línia crida un mètode, hi entra per veure’l per dins.
- Step Out. Acaba el mètode actual i torna a qui l’ha cridat.
- Inspecció de variables. Mentre està aturat, veus el valor de totes les variables en aquell instant.
flowchart LR
A["Poso un<br/>breakpoint"] --> B["Executo en<br/>mode debug"]
B --> C["El programa<br/>s'atura al breakpoint"]
C --> D["Inspecciono<br/>les variables"]
D --> E["Avanço<br/>pas a pas"]
E --> F{"Ja he trobat<br/>l'error?"}
F -->|No| D
F -->|Sí| G["El corregeixo"]
Al principi fa mandra aprendre’l, però un cop hi agafes el punt, trobar un error lògic passa de trigar una hora a trigar dos minuts.
En comptes d’endevinar què fa el programa, ho veus. Val moltíssim la pena invertir-hi una tarda.
8.9 Documentar amb Javadoc
Un codi ben documentat és més fàcil de mantenir i de compartir.
Java té un sistema estàndard de comentaris, el Javadoc, que s’escriu amb /** ... */ just abans de classes i mètodes.
Permet generar documentació web automàticament.
/**
* Calcula l'àrea d'un rectangle.
*
* @param base la base del rectangle (en cm), ha de ser positiva
* @param altura l'altura del rectangle (en cm), ha de ser positiva
* @return l'àrea del rectangle en cm²
*/
public static double areaRectangle(double base, double altura) {
return base * altura;
}Les etiquetes més habituals són:
| Etiqueta | Per a què serveix |
|---|---|
@param |
Descriu un paràmetre del mètode |
@return |
Descriu què retorna el mètode |
@throws |
Indica quines excepcions pot llançar |
@author |
Qui ha escrit el codi |
@since |
Des de quina versió existeix |
Un comentari // així o /* així */ és per a tu i el teu equip mentre llegiu el codi.
Un comentari Javadoc /** així */ descriu què fa i com s’usa un mètode o classe de cara a fora, i les eines el converteixen en documentació navegable.
Reserva el Javadoc per a la interfície pública del teu codi.
8.10 Resum
- Hi ha tres famílies d’errors: de compilació, d’execució (excepcions) i lògics (els més difícils de trobar).
- Una excepció és un objecte que Java llança quan alguna cosa va malament en execució; si no es captura, atura el programa i mostra la traça de pila.
try-catchcaptura excepcions i permet reaccionar-hi; pots tenir diversoscatch.finallys’executa sempre, hi hagi error o no.- Les excepcions comprovades (com
IOException) el compilador t’obliga a gestionar-les; les no comprovades (RuntimeExceptioni filles) no. throwllança una excepció;throwsdeclara que un mètode en pot llançar.assertvalida suposicions durant el desenvolupament (desactivat per defecte).- El debugger (breakpoints, pas a pas, inspecció) és l’eina professional per trobar errors lògics.
- Javadoc (
/** ... */amb@param,@return…) documenta el codi de manera estàndard.
8.11 Per practicar
- Divisió segura. Escriu un programa que demani dos enters i mostri la divisió, capturant la
ArithmeticExceptionsi el divisor és 0. - Lector robust. Fes un bucle que demani un nombre i, si l’usuari escriu text, capturi la
InputMismatchExceptioni torni a demanar-lo. - Validació amb
throw. Crea un mètoderetirar(double saldo, double import)que llanci una excepció si l’import supera el saldo. - Traça de pila. Provoca a posta una
NullPointerExceptioni unaArrayIndexOutOfBoundsException, i aprèn a llegir el missatge que apareix. - Debug guiat. Escriu un bucle que sumi malament (amb un error lògic a posta) i troba’l amb el debugger, no llegint el codi.
- Documenta. Afegeix comentaris Javadoc complets a tres mètodes que hagis escrit en capítols anteriors.