...
This noncompliant code example shows a Dimensions
class that contains three internal attributes, the length
, width
and height
of a rectangular box. The getVolumePackage()
method is designed to return the total volume required to hold the box, after accounting for packaging material which further adds 2 units to the dimensions of each side. Non-positive values of the dimensions of the box (exclusive of packaging material) are rejected during input validation. No dimension cannot be larger than 10. Also, the weight
of the object is passed in as an argument and cannot be more than 20 units.
Consider the case where the weight
is more than 20 units (21 units , herein this case). This causes an IllegalArgumentException
which is intercepted by the custom error reporter. While the logic restores the object's original state in the absence of this exception, the rollback code fails to execute in the event of an exception. Consequently, subsequent invocations of getVolumePackage()
produce incorrect results.
Code Block |
---|
|
class Dimensions {
private int length;
private int width;
private int height;
static public final int PADDING = 2;
static public final int MAX_DIMENSION = 10;
public Dimensions(int length, int width, int height) {
this.length = length;
this.width = width;
this.height = height;
}
protected int getVolumePackage(int weight) {
length += 2PADDING;
width += 2PADDING;
height += 2PADDING;
try {
if (length <= 2PADDING || width <= 2PADDING || height <= 2 PADDING ||
length > MAX_DIMENSION + PADDING || width > MAX_DIMENSION + PADDING ||
height > MAX_DIMENSION + PADDING || weight <= 0 || weight > 20) {
throw new IllegalArgumentException();
}
int volume = length * width * height; // 12 * 12 * 12 = 1728
length -=2 PADDING; width -= 2PADDING; height -= 2PADDING; // Revert back
return volume;
} catch (Throwable t) {
MyExceptionReporter mer = new MyExceptionReporter();
mer.report(t); // Sanitize
return -1; // Non-positive error code
}
}
public static void main(String[] args) {
Dimensions d = new Dimensions(10, 10, 10);
System.out.println(d.getVolumePackage(21)); // Prints -1 (error)
System.out.println(d.getVolumePackage(19)); // Prints 2744 instead of 1728
}
}
|
The catch clause is permitted under EX0 of guideline ERR14-J. Do not catch RuntimeException; it serves as a general filter passing exceptions to the MyExceptionReporter
class, which is dedicated to safely reporting exceptions, as recommended by guideline ERR01-J. Use a class dedicated to reporting exceptions. While this code only throws IllegalArgumentException
the catch clause is general enough to handle any exception, in case the try
block should be modified to throw other exceptionsNote that the explicitly thrown exception in this example is a stand-in for an exception that propagates from larger and more complex code in a real program. Consequently, this example should be considered purely notional.
Compliant Solution (Roll backRollback)
This compliant solution restores prior object state in the event of an exception.
Code Block |
---|
|
// ...
} catch (Throwable t) {
MyExceptionReporter mer = new MyExceptionReporter();
mer.report(t); // Sanitize
length -=2 PADDING; width -= 2PADDING; height -= 2PADDING; // Revert back
return -1;
}
|
Compliant Solution (finally
clause)
This compliant solution uses a finally
clause to perform rollback, guaranteeing that rollback occurs whether or not an error occurs.
Code Block |
---|
|
protected int getVolumePackage(int weight) {
length += PADDING;
width += PADDING;
height += PADDING;
try {
if (length <= PADDING || width <= PADDING || height <= PADDING ||
length > MAX_DIMENSION + PADDING || width > MAX_DIMENSION + PADDING ||
height > MAX_DIMENSION + PADDING || weight <= 0 || weight > 20) {
throw new IllegalArgumentException();
}
int volume = length * width * height; // 12 * 12 * 12 = 1728
return volume;
} catch (Throwable t) {
MyExceptionReporter mer = new MyExceptionReporter();
mer.report(t); // Sanitize
return -1; // Non-positive error code
} finally {
length -= PADDING; width -= PADDING; height -= PADDING; // Revert back
}
}
|
Compliant Solution (Input validation)
...
Code Block |
---|
|
protected int getVolumePackage(int weight) {
try {
if (length <= 0 || width <= 0 || height <= 0 ||
length > MAX_DIMENSION || width > MAX_DIMENSION || height > MAX_DIMENSION ||
weight <= 0 || weight > 20) {
throw new IllegalArgumentException(); // Validate first
}
} catch (Throwable t) { MyExceptionReporter mer = new MyExceptionReporter();
mer.report(t); // Sanitize
return -1;
}
length += 2PADDING;
width += 2PADDING;
height += 2PADDING;
int volume = length * width * height;
length -=2 PADDING; width -= 2PADDING; height -= 2PADDING;
return volume;
}
|
Compliant Solution (Modification Avoided)
This compliant solution entirely avoids the need to modify the object; consequently object invariants the fields cannot be violated and rollback is becomes unnecessary. This approach is preferred over the other compliant solutions , when possible. Note, however, that this approach but This approach may be infeasible for code more complicated than this notional examplemore complex code.
Code Block |
---|
|
protected int getVolumePackage(int weight) {
final static int packThickness = 2;
try {
if (length <= 0 || width <= 0 || height <= 0 ||
length > MAX_DIMENSION || width > MAX_DIMENSION || height > MAX_DIMENSION ||
weight <= 0 || weight > 20) {
throw new IllegalArgumentException(); // Validate first
}
} catch (Throwable t) { MyExceptionReporter mer = new MyExceptionReporter();
mer.report(t); // Sanitize
return -1;
}
int volume = (length + packThicknessPADDING) * (width + packThicknessPADDING) * (height + packThicknessPADDING);
return volume;
}
|
Risk Assessment
...