Il motivo principale per avere diversi tipi di eccezioni è di essere in grado di catturarli in modo selettivo.
Quindi la domanda che dovresti porci è: avrai mai un pezzo di codice in cui la conversione potrebbe fallire e vuoi solo catturare gli errori di conversione in una direzione e non nell'altra? Se è così, avere due classi di eccezione distinte è il modo migliore per andare. Se vuoi essere in grado di catturare entrambi nella stessa clausola, allora hai bisogno anche di una super classe comune.
Se hai difficoltà a prevedere in che modo vengono rilevate le eccezioni, passa a YAGNI e vai con la prima opzione. Puoi sempre aggiungere le sottoclassi in seguito se ne hai effettivamente bisogno.
A parte questo, penso che dovresti chiedersi se le eccezioni sono il modo giusto per segnalare un errore di conversione, perché in realtà l'errore è un'opzione prevista. Infatti molte API utilizzano semplicemente valori sentinella per questo.
Un altro bel modo per propagare gli errori sarebbe quello di avvolgere le informazioni di ritorno di ogni chiamata che può fallire in questo modo (in pseudo-codice):
class Outcome<Result, Error> {
const Bool success;
const Result result;
const Error error;
Result sure() {
if (success) return result;
else throw error;
}
}
Outcome<X, { value: Y, message: String }> convertYToX(Y y) {
if (suitable(y)) return Outcome{ success: true, result: convert(y) };
else return Outcome{ success: false, error: { }};
}
E poi lo fai:
handleX(convertXToY(myY).sure());//will throw an exception if an error occurred
Oppure controlla da solo il risultato:
var o = convertXToY(myY);
if (o.success)
handleX(o.result);
else {
log('error occured during conversion:');
log(o.error.message);
log('using default');
handleX(defaultX);
}