Prova / cattura o aggiungi un'eccezione alla firma del metodo per il metodo utilizzato da un sistema più grande?

2
public Path createPath(String name){
    return Files.createFile( Paths.get( name ) );
}

In createPath(String name) , il file java.nio.file.Files genera una java.io.IOException. Questo tuttavia deve essere utilizzato da una libreria più grande. E intendo enorme. Se aggiungo l'eccezione alla firma del metodo, ciò significherebbe che anche la libreria più grande dovrebbe aggiungere l'eccezione a tutti i metodi che la utilizzano. Avvolgendolo in una presa di prova si sarebbe presumibilmente salvato il giorno, ma questo avrebbe reso il codice dannatamente brutto. (ai miei occhi):

public Path createPath(String name){
    Path file = null;
    try {
        file = Files.createFile( Paths.get( name ) );
    } catch ( IOException e ) {
        e.printStackTrace();
    }
    return file;
}

Quale sarebbe l'approccio migliore per questo?

    
posta Rigo Sarmiento 01.08.2018 - 16:11
fonte

3 risposte

3

Sembra che tu abbia due opzioni in mente:

  1. createPath inghiotte l'eccezione e l'applicazione si arresta in modo anomalo da qualche parte su NullPointerException .
    • In realtà, il modo corretto per farlo è lanciare un RuntimeException o un Error o una sottoclasse di uno di essi.
  2. Aggiungi throws IOException all'intera callchain fino a main e lascia che l'applicazione si blocchi con IOException .

Ma l'effettiva intenzione dietro le eccezioni controllate (o le eccezioni in generale) è diversa:

  1. Aggiungi throws IOException al callchain fino al chiamante che sa cosa fare nel caso in cui un file non possa essere creato, e quel chiamante prenderà il IOException e lo gestirà.

Considera il seguente esempio:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Scanner;

public class App {
    private Scanner reader;

    public App() {
        reader = new Scanner(System.in);
    }

    public String getPathFromUser() {
        System.err.print("Enter a path: ");
        return reader.nextLine();
    }

    public Path createPath(String name) throws IOException {
        return Files.createFile(Paths.get(name));
    }

    public Path promptForPath() {
        while (true) {
            String path = getPathFromUser();
            try {
                return createPath(path);
            } catch (IOException e) {
                System.err.printf("Unable to create %s (%s)\n", path, e);
                continue;
            }
        }
    }

    public static void main(String[] args) {
        App app = new App();
        Path path = app.promptForPath();
        // do stuff with path
    }
}

Qui main chiama promptForPath che a sua volta chiama createPath .

  • promptForPath sa come gestire quel IOException (chiede all'utente un percorso diverso), quindi non deve essere contrassegnato con throw IOException .
  • createPath non sa come gestirlo, quindi deve essere throw IOException .
  • main non sa come gestirlo - ma non è necessario perché promptForPath lo ha già gestito.

È meglio inquinare i metodi nello stack di chiamate con le clausole throws o uglify createPath con try ... catch ?

Queste cose sono solo brutte e ingombranti quando vengono messe nel posto sbagliato. Quando vengono inseriti nel posto giusto, invece di essere un brutto inquinamento, possono essere effettivamente inseriti come parte della logica.

Il try ... catch nella createPath nella domanda è un brutto odore di codice - è chiaro che è lì per nascondere un problema. Il try ... catch nel mio promptForPath , d'altra parte, non è brutto perché sta gestendo un problema.

Allo stesso modo, throws IOException nei metodi di livello superiore è brutto - perché il tuo main dovrebbe lanciare un IOException ? Ma nei metodi più bassi che ottengono il percorso ha senso (" questo metodo si occupa dei percorsi e potrebbe incontrare un percorso non esistente ") in modo che non siano brutti - sono parti utili delle firme.

Un'altra cosa da considerare: se bolla le eccezioni fino in fondo, stai solo sostituendo un'eccezione con un'altra. Non sono sicuro che valga la pena il lavoro. Ma se lo stai solo ribollendo fino al punto in cui puoi gestirlo, la qualità della tua applicazione è migliorata - invece di andare in crash, ad esempio, stai notificando all'utente che il percorso che hanno inserito non è valido e gli chiedi di inserire un nuovo. Questo è un comportamento preferibile dalla parte dell'utente, e rende utile il refattore.

    
risposta data 01.08.2018 - 17:15
fonte
4

Innanzi tutto, la risposta corretta deve rispondere a questa semplice domanda:

What guarantees does this method have to make?

In breve, quali sono i requisiti? Se si richiede il metodo per creare un file e non crea quel file, non soddisfa il suo scopo. La ragione per cui non è possibile creare quel file può essere un numero qualsiasi di motivi che vanno dalle autorizzazioni, al percorso in cui si suppone che si crei il file in non esiste - o addirittura non può esistere.

A questo punto, hai 3 opzioni:

  • Ignora il problema - a volte non è un problema, ma non ignorare qualcosa e sperare che vada via. Non lo farà.
  • Restituisce uno stato che indica il successo o l'insuccesso, potenzialmente quale tipo di errore.
  • Genera un'eccezione che la biblioteca principale deve catturare. Naturalmente la prossima domanda è se dichiarare un'eccezione controllata o utilizzare un'eccezione di runtime.

In questo caso, se non è possibile creare il file, la libreria proprietaria potrebbe essere in grado di recuperare da tale condizione e tentare di creare un file diverso. Oppure potrebbe non essere ripristinabile, e ha bisogno di fare un po 'di pulizia prima che informi il codice finale usando quella libreria più grande che qualcosa è davvero molto sbagliato.

Ignorando silenziosamente il problema, si sta facendo in modo che la libreria presuma l'operazione completata correttamente quando non lo ha fatto. Le ipotesi non valide sono la causa di un numero qualsiasi di errori sottili e non così sottili. Desiderate comunicare chiaramente il successo o il fallimento dell'operazione in modo che il codice che utilizza quella funzione possa operare su uno stato noto.

La questione se l'eccezione sia verificata o meno è uno dei tipi di dolore che vuoi sopportare:

  • L'eccezione controllata è un'eccezione che deve essere gestita o inoltrata esplicitamente. Questo ha l'immediata pena di decidere cosa fare ora con l'eccezione nel tuo codice.
  • L'eccezione di runtime è un'eccezione che non ha bisogno di essere dichiarata, ma puoi prenderla se necessario. (Nelle lingue diverse da Java, questo è solitamente l'unico tipo di eccezione supportato). Il dolore che perdi è quando si verifica un'eccezione e non è gestita, causa l'arresto anomalo dell'applicazione utilizzando la tua libreria più grande.

Dovrai prendere le tue decisioni in base alla probabilità che pensi che verrà generata l'eccezione e a quale impatto avrà il sistema se l'eccezione non viene gestita direttamente.

    
risposta data 01.08.2018 - 16:56
fonte
-1

Chiaramente l'approccio "migliore" è il lancio dell'eccezione e modifica la libreria di conseguenza.

Quali sono le alternative? :

  • cattura e lancia un'eccezione già dichiarata
  • cattura e lancia un'eccezione non controllata
  • cattura e restituisce null

Tutti hanno i loro problemi e sono "meno buoni" del refactore completo

    
risposta data 01.08.2018 - 16:41
fonte

Leggi altre domande sui tag