středa 25. srpna 2010

Selži rychle, selži smysluplným typem

S tímhle problémem v uvozovkách si lámu hlavu pokaždé, když na něj narazím. Mám rád, nechci říci blbu vzdorný kód, ale kód který selže v případě chyby tak nejrychleji jak jen to půjde a zároveň s informací o tom co šlo špatně. Teď nemám na mysli zrovna chybu způsobenou uživatelem, ale třeba jiným programátorem včetně mě samotného. Jinými slovy můj kód je postaven na nějakých interních předpokladech. Otázka je, jakým správným typem výjimky reagovat na porušení těchto předpokladů.

Vezmeme si následující modelový příklad. Mějme třídu představující auto a jedním z jeho atributů je barva.

 
public class Car {
 public enum Color {RED, GREEN, BLUE};
 
 private final Color color;
 
 
 public  Color getColor() {
  return this.color;
 }
} 

Mějme API, které pracuje s touto třídou. Toto API je založené na předpokladu, že auto má vždycky nějakou barvu.

 
public boolean isWomanCar(Car car) {
 if(car == null) {
  throw new IllegalArgumentException("car must not be null"); 
 } 
 return car.getColor().equals(RED);
} 

Kód správně kontroluje vstupní argument, ale zároveň je založen na předpokladu, že barva nebude nikdy null (v rámci ilustrace pomineme fakt, že by šel přepsat na null safe v tomhle případě). Protože chci selhat rychle a né někde v hlubinách na NullPointerException upravím kód, tak abych můj předpoklad explicitně vyjádřil.

 
public boolean isWomanCar(Car car) {
 if(car == null) {
  throw new IllegalArgumentException("car must not be null"); 
 } 
 
 if(car.getColor() == null) {
     throw new ?????? 
 }
 return car.getColor().equals(RED);
} 

Předpoklad jsem zachytil, ale váhám jaký typ výjimky je proto vhodný. V podstatě jsem svůj výběr zúžil na IllegalArgumentExceotion, AssertionError a nějaký vlastní typ AssumptionException. Proti IllegalArgumentException hovoří to, že jí používám pro předpoklady vstupních argumentů. Na druhou stranu já v podstatě kontroluji předpoklad, který se týká vstupního předpokladu. Proti AssertionError zase, že by se neměla používat na cokoliv co souvisí se vstupem, ale na druhou stranu tohle použití se nejvíce blíží k čemu byla navržena. AssumptionException nechci protože zavádí nějaký můj vlastní typ, jehož významu rozumím já, ale těžko nějaký vývojář, který bude API používat.

Výše uvedený příklad je velice zjednodušený, aby se to něm pěkně ilustrovalo. Ale určitě i vy máte spoustu kódu, který na něco spoléhá. Já nakonec asi stejně skončím u IllegalArgumentException, protože mi přijde jako nejmenší zlo.