29
Anotações Capítulo 7: Error Handling
public class DeviceController {
…
public void sendShutDown() {
DeviceHandle handle = getHandle(DEV1);
// Check the state of the device
if (handle != DeviceHandle.INVALID) {
// Save the device status to the record field
retrieveDeviceRecord(handle);
// If not suspended, shut down
if (record.getStatus() != DEVICE_SUSPENDED) {
pauseDevice(handle);
clearDeviceWorkQueue(handle);
closeDevice(handle);
} else {
logger.log("Device suspended. Unable to shut down");
}
} else {
logger.log("Invalid handle for: " + DEV1.toString());
}
}
...
}
public class DeviceController {
…
public void sendShutDown() {
try {
tryToShutDown();
} catch (DeviceShutDownError e) {
logger.log(e);
}
}
private void tryToShutDown() throws DeviceShutDownError {
DeviceHandle handle = getHandle(DEV1);
DeviceRecord record = retrieveDeviceRecord(handle);
pauseDevice(handle);
clearDeviceWorkQueue(handle);
closeDevice(handle);
}
private DeviceHandle getHandle(DeviceID id) {
…
throw new DeviceShutDownError("Invalid handle for: " + id.toString());
…
}
...
}
try
, você declara que aquela execução pode ser cancelada a qualquer momento e então continuar no catch.try
são como transações. Seu catch
tem que deixar seu programa num estado consistente, não importa o que aconteça no try
.try...catch...finally
quando for escrever um código que talvez lance exceções. Isso ajuda a definir o que o usuário do código deve esperar, independente do que ocorra de errado no código que é executado no try
.- Um código que acesse um arquivo e consulte alguns objetos em série:
- Começamos com um teste de unidade que mostra como capturar uma exceção se o arquivo não existir:
@Test(expected = StorageException.class)
public void retrieveSectionShouldThrowOnInvalidFileName() {
sectionStore.retrieveSection("invalid - file");
}
public List<RecordedGrip> retrieveSection(String sectionName) {
// dummy return until we have a real implementation
return new ArrayList<RecordedGrip>();
}
public List<RecordedGrip> retrieveSection(String sectionName) {
try {
FileInputStream stream = new FileInputStream(sectionName);
} catch (Exception e) {
throw new StorageException("retrieval error", e);
}
return new ArrayList<RecordedGrip>();
}
FileInputStream
:
public List<RecordedGrip> retrieveSection(String sectionName) {
try {
FileInputStream stream = new FileInputStream(sectionName);
stream.close();
} catch (FileNotFoundException e) {
throw new StorageException("retrieval error”, e);
}
return new ArrayList<RecordedGrip>();
}
stack trace
de qualquer exceção. Porém ela não consegue dizer o motivo da falha da operação.ACMEPort port = new ACMEPort(12);
try {
port.open();
} catch (DeviceResponseException e) {
reportPortError(e);
logger.log("Device response exception", e);
} catch (ATM1212UnlockedException e) {
reportPortError(e);
logger.log("Unlock exception", e);
} catch (GMXError e) {
reportPortError(e);
logger.log("Device response exception");
} finally {
…
}
LocalPort port = new LocalPort(12);
try {
port.open();
} catch (PortDeviceFailure e) {
reportError(e);
logger.log(e.getMessage(), e);
} finally {
…
}
LocalPort
é um simples wrapper (empacotador) que captura e traduz as exceções:
public class LocalPort {
private ACMEPort innerPort;
public LocalPort(int portNumber) {
innerPort = new ACMEPort(portNumber);
}
public void open() {
try {
innerPort.open();
} catch (DeviceResponseException e) {
throw new PortDeviceFailure(e);
} catch (ATM1212UnlockedException e) {
throw new PortDeviceFailure(e);
} catch (GMXError e) {
throw new PortDeviceFailure(e);
}
}
…
}
try {
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
} catch(MealExpensesNotFound e) {
m_total += getMealPerDiem();
}
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
ExpenseReportDAO
para que ele sempre retorne um objeto MealExpenses
. Se não houver gastos com refeições, ele retorna um objeto PerDiemMealExpenses
que retorna a diária como seu total:
public class PerDiemMealExpenses implements MealExpenses {
public int getTotal() {
// retorna a ajuda de custo padrão
}
}
Isso se chama padrão Special Case (Padrão de caso especial). Você cria uma classe ou configure um objeto de modo que ele trate de um caso especial para você. Com isso o código do cliente não precisa lidar com um comportamento diferente. Este fica encapsulado num objeto de caso especial.
public void registerItem(Item item) {
if (item != null) {
ItemRegistry registry = peristentStore.getItemRegistry();
if (registry != null) {
Item existing = registry.getItem(item.getID());
if (existing.getBillingPeriod().hasRetailOwner()) {
existing.register(item);
}
}
}
}
null
, estamos criando mais trabalho para nós mesmos e jogando problemas em cima de nossos chamadores. Só basta esquecer uma verificação null
para que o aplicativo fique fora de controle.null
na segunda linha do if
aninhado? E se peristentStore
fosse null
?null
. null
de um método, lance uma exceção ou um objeto SPECIAL CASE!null
use o empacotamento ou o objeto de caso especial.List<Employee> employees = getEmployees();
if (employees != null) {
for(Employee e : employees) {
totalPay += e.getPay();
}
}
null
:
List<Employee> employees = getEmployees();
for(Employee e : employees) {
totalPay += e.getPay();
}
E no método
getEmployees
:public List<Employee> getEmployees() {
if( .. there are no employees .. )
return Collections.emptyList();
}
NullPointerException
e o código será mais limpo.null
dos métodos é ruim, mas passar null
para eles é pior ainda.null
, devemos evitar passá-lo.public class MetricsCalculator {
public double xProjection(Point p1, Point p2) {
return (p2.x – p1.x) * 1.5;
}
…
}
null
para o método acima receberemos uma NullPointerException
!public class MetricsCalculator {
public double xProjection(Point p1, Point p2) {
if (p1 == null || p2 == null) {
throw InvalidArgumentException("Invalid argument for MetricsCalculator.xProjection");
}
return (p2.x – p1.x) * 1.5;
}
}
null
é sinal de problema e pode gerar mais erros por descuido.29