A DESCRIPTION OF THE PROBLEM :
Hey, I have a(n another) suggestion to make writing programs in the Java language safer and less bug-prone. I'm sorry in advance for the barrage of features (I assume it could be a better idea to separate them into different enhancement requests, but I think they all come in a whole idea bundle).
What concerns me today is RuntimeExceptions and Errors. These two are the features that allow the developer (and the language/core libraries) to halt execution of a method without any particular checks. This means the failure point is hidden, unless is specified in some place like the JavaDoc for a throwing method or the Java Language Specification, being a fault point for the bugs to appear.
What I suggest is adding a Strict Throwable mode, which would prevent RuntimeExceptions and Errors to be ignored - they will all have to abide the same rules as Exceptions and Throwable: be handled by a try-catch block or a `throws` keyword.
As an example, such code as following:
public int asInt(String input) {
return Integer.parseInt(input);
}
Must become (or with `catch (NumberFormatException e)`):
public int asInt(String input) throws NumberFormatException {
return Integer.parseInt(input);
}
Which means the forgotten runtime exception now must be handled by the developer, thus reducing amount of fault points made by a novice or a developer who forgot to do the proper checks in their code.
As an another example:
public ExampleType build() {
return new ExampleType();
}
Will become (or with `catch (OutOfMemoryError e)`):
public ExampleType build() throws OutOfMemoryError {
return new ExampleType();
}
Which greately increases the usability of the language as a system development tool (like for an operating system).
This means following actions are covered by this (list or individual points may be incomplete):
- NullPointerException - method calls on non-effectively-not-null objects (Effectively Non-null variable has been checked for `null` value, and thus does not contain it in its following usages).
- OutOfMemoryError - `new` keyword.
- StackOverflowError - any method call (might need some extra thought).
- any of the above; a subclass of RuntimeException or Error - method call that throws the throwable.
This means if the developer intents to pass the errors up the stack, they will have to list all possible throwables thrown by the method, which may be not very convenient. The usual approach is to use a throwable superclass, common for all thrown throwables in the method:
public void anyThrows() throws Exception { // but actually `throws Exception1, Exception2`
...
}
This means the developer will have to handle not only the particular throwables that developer wanted to handle, but also the supertype:
try {
anyThrows();
} catch (Exception1 e1) {
...
} catch (Exception2 e2) {
...
} catch (Exception e) { // here, even if both cases above are exhaustive for the method, the method signature does not say that
...
}
My suggestion for this is to add another syntax for Inferred Throwables:
public void anyThrows() throws { // alternatively, `throws Exception1, *` to allow the developer to also list their throwables explicitly for the self-documenting code
...
}
This means that the compiler has to scan through the code and find all throwables (or collect them while parsing) that may be thrown by the method (either by the `throw` keyword or usages mentioned above, like the `new` keyword).
A problem may arise is figuring out what to do when calling a non-strict-throwable-mode (e.g. existing) method. Here are the two solutions I thought of:
- Assume that the method has `throws Throwable` (but this implies that even if a method is thoroughly checked and has all the throws, but isn't compiled in the Strict Throwable mode, it will still use the superclass for the Strict Throwable mode code, so I do not recommend this approach).
- Figure out the method's contract by scanning its bytecode (look for object construction, method calls, throws, etc). Basically, assume `throws *`.
The Strict Throwable mode may be done as a compiler flag, `module-info.java` setting, or somewhere directly in the code syntax (`public strict class ...`, `public strict void ...`, `@Strict`), to allow for the non-rewritten legacy code to still compile.
As additional feature, I suggest Try expressions. Please tell me if it needs to be separated into a different enhancement suggestion, even though I think it is greatly related to this. The premise is simple:
LongTypeYouDontWantToType longVarName;
try {
longVarName = attempt();
} catch (Exception1 e1) {
longVarName = onError1(e1);
} catch (Exception2 e2) {
longVarName = onError2(e2);
} catch (Exception3 e3) {
longVarName = onError3(e3);
}
Becomes (also think of a `->` syntax and shortening `catch (E e) methodOfE(e)` to `catch methodOfE` or `catch ExampleClass::methodOfE`):
var longVarName = try attempt()
catch (Exception1 e1) onError1(e1)
catch (Exception2 e2) onError2(e2)
catch (Exception3 e3) onError3(e3);
And a `catch (Exception _)` for whenever you will use the reserved underline variable name.
P.S. Many parts of this suggestion are inspired by the new Jakt language (https://github.com/SerenityOS/jakt), developed for SerenityOS as a safe alternative to C++.
P.P.S. A report subcomponent for java.lang#Throwable would be great.
Hey, I have a(n another) suggestion to make writing programs in the Java language safer and less bug-prone. I'm sorry in advance for the barrage of features (I assume it could be a better idea to separate them into different enhancement requests, but I think they all come in a whole idea bundle).
What concerns me today is RuntimeExceptions and Errors. These two are the features that allow the developer (and the language/core libraries) to halt execution of a method without any particular checks. This means the failure point is hidden, unless is specified in some place like the JavaDoc for a throwing method or the Java Language Specification, being a fault point for the bugs to appear.
What I suggest is adding a Strict Throwable mode, which would prevent RuntimeExceptions and Errors to be ignored - they will all have to abide the same rules as Exceptions and Throwable: be handled by a try-catch block or a `throws` keyword.
As an example, such code as following:
public int asInt(String input) {
return Integer.parseInt(input);
}
Must become (or with `catch (NumberFormatException e)`):
public int asInt(String input) throws NumberFormatException {
return Integer.parseInt(input);
}
Which means the forgotten runtime exception now must be handled by the developer, thus reducing amount of fault points made by a novice or a developer who forgot to do the proper checks in their code.
As an another example:
public ExampleType build() {
return new ExampleType();
}
Will become (or with `catch (OutOfMemoryError e)`):
public ExampleType build() throws OutOfMemoryError {
return new ExampleType();
}
Which greately increases the usability of the language as a system development tool (like for an operating system).
This means following actions are covered by this (list or individual points may be incomplete):
- NullPointerException - method calls on non-effectively-not-null objects (Effectively Non-null variable has been checked for `null` value, and thus does not contain it in its following usages).
- OutOfMemoryError - `new` keyword.
- StackOverflowError - any method call (might need some extra thought).
- any of the above; a subclass of RuntimeException or Error - method call that throws the throwable.
This means if the developer intents to pass the errors up the stack, they will have to list all possible throwables thrown by the method, which may be not very convenient. The usual approach is to use a throwable superclass, common for all thrown throwables in the method:
public void anyThrows() throws Exception { // but actually `throws Exception1, Exception2`
...
}
This means the developer will have to handle not only the particular throwables that developer wanted to handle, but also the supertype:
try {
anyThrows();
} catch (Exception1 e1) {
...
} catch (Exception2 e2) {
...
} catch (Exception e) { // here, even if both cases above are exhaustive for the method, the method signature does not say that
...
}
My suggestion for this is to add another syntax for Inferred Throwables:
public void anyThrows() throws { // alternatively, `throws Exception1, *` to allow the developer to also list their throwables explicitly for the self-documenting code
...
}
This means that the compiler has to scan through the code and find all throwables (or collect them while parsing) that may be thrown by the method (either by the `throw` keyword or usages mentioned above, like the `new` keyword).
A problem may arise is figuring out what to do when calling a non-strict-throwable-mode (e.g. existing) method. Here are the two solutions I thought of:
- Assume that the method has `throws Throwable` (but this implies that even if a method is thoroughly checked and has all the throws, but isn't compiled in the Strict Throwable mode, it will still use the superclass for the Strict Throwable mode code, so I do not recommend this approach).
- Figure out the method's contract by scanning its bytecode (look for object construction, method calls, throws, etc). Basically, assume `throws *`.
The Strict Throwable mode may be done as a compiler flag, `module-info.java` setting, or somewhere directly in the code syntax (`public strict class ...`, `public strict void ...`, `@Strict`), to allow for the non-rewritten legacy code to still compile.
As additional feature, I suggest Try expressions. Please tell me if it needs to be separated into a different enhancement suggestion, even though I think it is greatly related to this. The premise is simple:
LongTypeYouDontWantToType longVarName;
try {
longVarName = attempt();
} catch (Exception1 e1) {
longVarName = onError1(e1);
} catch (Exception2 e2) {
longVarName = onError2(e2);
} catch (Exception3 e3) {
longVarName = onError3(e3);
}
Becomes (also think of a `->` syntax and shortening `catch (E e) methodOfE(e)` to `catch methodOfE` or `catch ExampleClass::methodOfE`):
var longVarName = try attempt()
catch (Exception1 e1) onError1(e1)
catch (Exception2 e2) onError2(e2)
catch (Exception3 e3) onError3(e3);
And a `catch (Exception _)` for whenever you will use the reserved underline variable name.
P.S. Many parts of this suggestion are inspired by the new Jakt language (https://github.com/SerenityOS/jakt), developed for SerenityOS as a safe alternative to C++.
P.P.S. A report subcomponent for java.lang#Throwable would be great.