The goal of an Exception Fact Sheet is to reveal the design of exception handling in an application.
--Maxence, Martin
For feedback, please contact Martin
Number of Classes | 185 |
Number of Domain Exception Types (Thrown or Caught) | 11 |
Number of Domain Checked Exception Types | 5 |
Number of Domain Runtime Exception Types | 3 |
Number of Domain Unknown Exception Types | 3 |
nTh = Number of Throw | 78 |
nTh = Number of Throw in Catch | 36 |
Number of Catch-Rethrow (may not be correct) | 9 |
nC = Number of Catch | 102 |
nCTh = Number of Catch with Throw | 36 |
Number of Empty Catch (really Empty) | 5 |
Number of Empty Catch (with comments) | 4 |
Number of Empty Catch | 9 |
nM = Number of Methods | 1003 |
nbFunctionWithCatch = Number of Methods with Catch | 64 / 1003 |
nbFunctionWithThrow = Number of Methods with Throw | 55 / 1003 |
nbFunctionWithThrowS = Number of Methods with ThrowS | 159 / 1003 |
nbFunctionTransmitting = Number of Methods with "Throws" but NO catch, NO throw (only transmitting) | 136 / 1003 |
P1 = nCTh / nC | 35.3% (0.353) |
P2 = nMC / nM | 6.4% (0.064) |
P3 = nbFunctionWithThrow / nbFunction | 5.5% (0.055) |
P4 = nbFunctionTransmitting / nbFunction | 13.6% (0.136) |
P5 = nbThrowInCatch / nbThrow | 46.2% (0.462) |
R2 = nCatch / nThrow | 1.308 |
A1 = Number of Caught Exception Types From External Libraries | 16 |
A2 = Number of Reused Exception Types From External Libraries (thrown from application code) | 7 |
W1 is a rough estimation of the richness of the exception model. It does not take into account the inheritance relationships between domain exceptions.
Proportion P1 measures the overall exception flow. According to our experience, it varies from 5% to 70%. Early-catch design generally yields a low P1, libraries that must warn clients about errors (e.g. databases) generally have a high P1.
Proportion P2 measures the dispersion of catch blocks in the application. According to our experience, it varies from 2% to 15%. A small P2 indicates a rather centralized management of errors.
R1 shows how many exceptions types from libraries (incl. JDK) are thrown from application code. For instance, IllegalArgumentException comes from the JDK but is used in many applications.
A1 measures the awareness of the application to library exceptions. A high value of A1 means either that the application is polluted with checked exceptions or that it is able to apply specific recovery depending on the library exception.
Each exception that is used at least once in the project is a dot. A orange dot represents a domain exception that is defined in the application. A blue dot exception is defined in the JDK or in a library. The x-axis represents the number of times an exception is caught, the y-axis the number of times an exception is thrown.
A (Domain) exception is defined in the application. A (Lib) exception is defined in the JDK or in a library. An exception can be thrown, thrown from within a catch, or declared in the signature of a method (usually for checked exceptions). Hovering over a number triggers showing code snippets from the application code.
A (Domain) exception is defined in the application. A (Lib) exception is defined in the JDK or in a library. An exception can be caught, and it happens that the catch block contains a throw (e.g. for wrapping a low-level exception). Hovering over a number triggers showing code snippets from the application code.
There is a common practice of throwing exceptions from within a catch block (e.g. for wrapping a low-level exception). The following table summarizes the usage of this practice in the application. The last column gives the number of times it happens for a pair of exceptions. The graph below the table graphically renders the same information. For a given node, its color represents its origin (blue means library exception, orange means domain exception); the left-most number is the number of times it is thrown, the right-most is the number of times it is caught.
Not all exceptions are thrown AND caught in the same project. The following table gives the exceptions types with respect to this. The lower left hand side sell lists all exceptions thrown but not caught (prevalent for libraries), the upper right-hand side lists all exceptions caught but not thrown (usually coming from external dependencies).
Thrown | Not Thrown | |||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Caught |
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
Not caught |
|
The following shows the methods that are called inside catch blocks (first column) and finally blocks (second column). For each method, we give the number of times it is called in a catch block (second sub-column), and the total number of calls (third sub-column). If the method name is red, it means that it is only called from catch/finally blocks. Hovering over a number triggers showing code snippets from the application code.
Catch | Finally | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
This table concatenates the results of the previous tables.
Checked/Runtime | Type | Exception | Thrown | Thrown from Catch | Declared | Caught directly | Caught with Thrown |
Caught with Thrown Runtime |
---|---|---|---|---|---|---|---|---|
unknown | (Lib) | . | 0 | 0 | 0 | 0 | 0 | 0 |
unknown | (Domain) |
ArrayComparisonFailure
public class ArrayComparisonFailure extends AssertionError { private static final long serialVersionUID= 1L; private List<Integer> fIndices= new ArrayList<Integer>(); private final String fMessage; private final AssertionError fCause; /** * Construct a new <code>ArrayComparisonFailure</code> with an error text and the array's * dimension that was not equal * @param cause the exception that caused the array's content to fail the assertion test * @param index the array position of the objects that are not equal. * @see Assert#assertArrayEquals(String, Object[], Object[]) */ public ArrayComparisonFailure(String message, AssertionError cause, int index) { fMessage= message; fCause= cause; addDimension(index); } public void addDimension(int index) { fIndices.add(0, index); } @Override public String getMessage() { StringBuilder builder= new StringBuilder(); if (fMessage != null) builder.append(fMessage); builder.append("arrays first differed at element "); for (int each : fIndices) { builder.append("["); builder.append(each); builder.append("]"); } builder.append("; "); builder.append(fCause.getMessage()); return builder.toString(); } /** * {@inheritDoc} */ @Override public String toString() { return getMessage(); } } |
1
|
1
|
10
|
1
|
1
|
0 |
unknown | (Lib) | AssertionError |
5
|
0 | 0 |
2
|
1
|
0 |
unknown | (Domain) |
AssertionFailedError
public class AssertionFailedError extends AssertionError { private static final long serialVersionUID= 1L; public AssertionFailedError() { } public AssertionFailedError(String message) { super(defaultString(message)); } private static String defaultString(String message) { return message == null ? "" : message; } } |
2
|
0 | 0 |
1
|
0 | 0 |
runtime | (Domain) |
AssumptionViolatedException
public class AssumptionViolatedException extends RuntimeException implements SelfDescribing { private static final long serialVersionUID= 1L; private final Object fValue; private final Matcher<?> fMatcher; public AssumptionViolatedException(Object value, Matcher<?> matcher) { super(value instanceof Throwable ? (Throwable) value : null); fValue= value; fMatcher= matcher; } public AssumptionViolatedException(String assumption) { this(assumption, null); } @Override public String getMessage() { return StringDescription.asString(this); } public void describeTo(Description description) { if (fMatcher != null) { description.appendText("got: "); description.appendValue(fValue); description.appendText(", expected: "); description.appendDescriptionOf(fMatcher); } else { description.appendText("failed assumption: " + fValue); } } } |
1
|
0 | 0 |
9
|
5
|
0 |
unknown | (Lib) | ClassCastException | 0 | 0 | 0 |
1
|
1
|
0 |
unknown | (Lib) | ClassNotFoundException | 0 | 0 |
1
|
4
|
0 | 0 |
unknown | (Domain) |
ComparisonFailure
public class ComparisonFailure extends AssertionFailedError { private static final int MAX_CONTEXT_LENGTH= 20; private static final long serialVersionUID= 1L; private String fExpected; private String fActual; /** * Constructs a comparison failure. * @param message the identifying message or null * @param expected the expected string value * @param actual the actual string value */ public ComparisonFailure (String message, String expected, String actual) { super (message); fExpected= expected; fActual= actual; } /** * Returns "..." in place of common prefix and "..." in * place of common suffix between expected and actual. * * @see Throwable#getMessage() */ @Override public String getMessage() { return new ComparisonCompactor(MAX_CONTEXT_LENGTH, fExpected, fActual).compact(super.getMessage()); } /** * Gets the actual string value * @return the actual string value */ public String getActual() { return fActual; } /** * Gets the expected string value * @return the expected string value */ public String getExpected() { return fExpected; } }public class ComparisonFailure extends AssertionError { /** * The maximum length for fExpected and fActual. If it is exceeded, the strings should be shortened. * @see ComparisonCompactor */ private static final int MAX_CONTEXT_LENGTH= 20; private static final long serialVersionUID= 1L; private String fExpected; private String fActual; /** * Constructs a comparison failure. * @param message the identifying message or null * @param expected the expected string value * @param actual the actual string value */ public ComparisonFailure (String message, String expected, String actual) { super (message); fExpected= expected; fActual= actual; } /** * Returns "..." in place of common prefix and "..." in * place of common suffix between expected and actual. * * @see Throwable#getMessage() */ @Override public String getMessage() { return new ComparisonCompactor(MAX_CONTEXT_LENGTH, fExpected, fActual).compact(super.getMessage()); } /** * Returns the actual string value * @return the actual string value */ public String getActual() { return fActual; } /** * Returns the expected string value * @return the expected string value */ public String getExpected() { return fExpected; } private static class ComparisonCompactor { private static final String ELLIPSIS= "..."; private static final String DELTA_END= "]"; private static final String DELTA_START= "["; /** * The maximum length for <code>expected</code> and <code>actual</code>. When <code>contextLength</code> * is exceeded, the Strings are shortened */ private int fContextLength; private String fExpected; private String fActual; private int fPrefix; private int fSuffix; /** * @param contextLength the maximum length for <code>expected</code> and <code>actual</code>. When contextLength * is exceeded, the Strings are shortened * @param expected the expected string value * @param actual the actual string value */ public ComparisonCompactor(int contextLength, String expected, String actual) { fContextLength= contextLength; fExpected= expected; fActual= actual; } private String compact(String message) { if (fExpected == null || fActual == null || areStringsEqual()) return Assert.format(message, fExpected, fActual); findCommonPrefix(); findCommonSuffix(); String expected= compactString(fExpected); String actual= compactString(fActual); return Assert.format(message, expected, actual); } private String compactString(String source) { String result= DELTA_START + source.substring(fPrefix, source.length() - fSuffix + 1) + DELTA_END; if (fPrefix > 0) result= computeCommonPrefix() + result; if (fSuffix > 0) result= result + computeCommonSuffix(); return result; } private void findCommonPrefix() { fPrefix= 0; int end= Math.min(fExpected.length(), fActual.length()); for (; fPrefix < end; fPrefix++) { if (fExpected.charAt(fPrefix) != fActual.charAt(fPrefix)) break; } } private void findCommonSuffix() { int expectedSuffix= fExpected.length() - 1; int actualSuffix= fActual.length() - 1; for (; actualSuffix >= fPrefix && expectedSuffix >= fPrefix; actualSuffix--, expectedSuffix--) { if (fExpected.charAt(expectedSuffix) != fActual.charAt(actualSuffix)) break; } fSuffix= fExpected.length() - expectedSuffix; } private String computeCommonPrefix() { return (fPrefix > fContextLength ? ELLIPSIS : "") + fExpected.substring(Math.max(0, fPrefix - fContextLength), fPrefix); } private String computeCommonSuffix() { int end= Math.min(fExpected.length() - fSuffix + 1 + fContextLength, fExpected.length()); return fExpected.substring(fExpected.length() - fSuffix + 1, end) + (fExpected.length() - fSuffix + 1 < fExpected.length() - fContextLength ? ELLIPSIS : ""); } private boolean areStringsEqual() { return fExpected.equals(fActual); } } } |
2
|
0 | 0 | 0 | 0 | 0 |
checked | (Domain) |
CouldNotGenerateValueException
public static class CouldNotGenerateValueException extends Exception { private static final long serialVersionUID= 1L; } |
2
|
1
|
9
|
1
|
0 | 0 |
checked | (Domain) |
CouldNotReadCoreException
public class CouldNotReadCoreException extends Exception { private static final long serialVersionUID= 1L; /** * Constructs */ public CouldNotReadCoreException(Throwable e) { super(e); } } |
1
|
1
|
1
|
1
|
0 | 0 |
runtime | (Lib) | Error |
1
|
0 | 0 | 0 | 0 | 0 |
checked | (Lib) | Exception |
5
|
2
|
47
|
14
|
4
|
2 |
unknown | (Lib) | FailedBefore |
4
|
4
|
2
|
2
|
0 | 0 |
checked | (Lib) | IOException | 0 | 0 |
7
|
2
|
0 | 0 |
unknown | (Lib) | IllegalAccessException | 0 | 0 |
7
|
6
|
4
|
3 |
runtime | (Lib) | IllegalArgumentException | 4 | 0 |
2
|
2
|
2
|
2 |
runtime | (Lib) | IllegalStateException |
2
|
0 | 0 | 0 | 0 | 0 |
checked | (Domain) |
InitializationError
public class InitializationError extends Exception { private static final long serialVersionUID= 1L; private final List<Throwable> fErrors; /** * Construct a new {@code InitializationError} with one or more * errors {@code errors} as causes */ public InitializationError(List<Throwable> errors) { fErrors= errors; } public InitializationError(Throwable error) { this(Arrays.asList(error)); } /** * Construct a new {@code InitializationError} with one cause * with message {@code string} */ public InitializationError(String string) { this(new Exception(string)); } /** * Returns one or more Throwables that led to this initialization error. */ public List<Throwable> getCauses() { return fErrors; } } |
7
|
2
|
23
|
3
|
2
|
2 |
unknown | (Lib) | InstantiationException | 0 | 0 |
5
|
1
|
0 | 0 |
unknown | (Lib) | InterruptedException | 0 | 0 |
1
|
2
|
0 | 0 |
unknown | (Lib) | InvocationTargetException | 0 | 0 |
2
|
11
|
5
|
0 |
checked | (Domain) |
MultipleFailureException
public class MultipleFailureException extends Exception { private static final long serialVersionUID= 1L; private final List<Throwable> fErrors; public MultipleFailureException(List<Throwable> errors) { fErrors= new ArrayList<Throwable>(errors); } public List<Throwable> getFailures() { return Collections.unmodifiableList(fErrors); } @Override public String getMessage() { StringBuilder sb = new StringBuilder( String.format("There were %d errors:", fErrors.size())); for (Throwable e : fErrors) { sb.append(String.format("\n %s(%s)", e.getClass().getName(), e.getMessage())); } return sb.toString(); } /** * Asserts that a list of throwables is empty. If it isn't empty, * will throw {@link MultipleFailureException} (if there are * multiple throwables in the list) or the first element in the list * (if there is only one element). * * @param errors list to check * @throws Throwable if the list is not empty */ @SuppressWarnings("deprecation") public static void assertEmpty(List<Throwable> errors) throws Throwable { if (errors.isEmpty()) return; if (errors.size() == 1) throw errors.get(0); /* * Many places in the code are documented to throw * org.junit.internal.runners.model.MultipleFailureException. * That class now extends this one, so we throw the internal * exception in case developers have tests that catch * MultipleFailureException. */ throw new org.junit.internal.runners.model.MultipleFailureException(errors); } |
1 | 0 | 0 | 0 | 0 | 0 |
unknown | (Lib) | NoSuchMethodException | 0 | 0 |
3
|
7
|
1
|
0 |
checked | (Domain) |
NoTestsRemainException
public class NoTestsRemainException extends Exception { private static final long serialVersionUID = 1L; } |
2
|
0 |
6
|
3
|
1
|
0 |
unknown | (Lib) | NumberFormatException | 0 | 0 | 0 |
1
|
0 | 0 |
runtime | (Domain) |
ParameterizedAssertionError
public class ParameterizedAssertionError extends RuntimeException { private static final long serialVersionUID = 1L; public ParameterizedAssertionError(Throwable targetException, String methodName, Object... params) { super(String.format("%s(%s)", methodName, join(", ", params)), targetException); } @Override public boolean equals(Object obj) { return toString().equals(obj.toString()); } public static String join(String delimiter, Object... params) { return join(delimiter, Arrays.asList(params)); } public static String join(String delimiter, Collection<Object> values) { StringBuffer buffer = new StringBuffer(); Iterator<Object> iter = values.iterator(); while (iter.hasNext()) { Object next = iter.next(); buffer.append(stringValueOf(next)); if (iter.hasNext()) { buffer.append(delimiter); } } return buffer.toString(); } private static String stringValueOf(Object next) { try { return String.valueOf(next); } catch (Throwable e) { return "[toString failed]"; } } } |
1
|
0 | 0 | 0 | 0 | 0 |
runtime | (Lib) | RuntimeException |
12
|
10
|
0 | 0 | 0 | 0 |
runtime | (Domain) |
StoppedByUserException
public class StoppedByUserException extends RuntimeException { private static final long serialVersionUID= 1L; } |
1
|
0 |
1
|
1
|
1
|
0 |
unknown | (Lib) | ThreadDeath | 0 | 0 | 0 |
1
|
1
|
0 |
checked | (Lib) | Throwable | 0 | 0 |
45
|
25
|
7
|
1 |
unknown | (Lib) | TimeoutException | 0 | 0 | 0 |
1
|
0 | 0 |
nF = Number of Finally | 15 |
nF = Number of Try-Finally (without catch) | 8 |
Number of Methods with Finally (nMF) | 14 / 1003 (1.4%) |
Number of Finally with a Continue | 0 |
Number of Finally with a Return | 0 |
Number of Finally with a Throw | 0 |
Number of Finally with a Break | 0 |
Number of different exception types thrown | 18 |
Number of Domain exception types thrown | 11 |
Number of different exception types caught | 24 |
Number of Domain exception types caught | 8 |
Number of exception declarations in signatures | 181 |
Number of different exceptions types declared in method signatures | 18 |
Number of library exceptions types declared in method signatures | 12 |
Number of Domain exceptions types declared in method signatures | 6 |
Number of Catch with a continue | 0 |
Number of Catch with a return | 28 |
Number of Catch with a Break | 0 |
nbIf = Number of If | 266 |
nbFor = Number of For | 105 |
Number of Method with an if | 183 / 1003 |
Number of Methods with a for | 98 / 1003 |
Number of Method starting with a try | 25 / 1003 (2.5%) |
Number of Expressions | 4762 |
Number of Expressions in try | 524 (11%) |