-
Notifications
You must be signed in to change notification settings - Fork 179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
improve error handling and logging #1311
Conversation
PR Summary
This collection of changes overall leans towards refactoring the code for improved readability, maintainability, and performance. |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1311 +/- ##
============================================
+ Coverage 91.82% 91.97% +0.14%
+ Complexity 3159 3152 -7
============================================
Files 316 316
Lines 6229 6232 +3
Branches 633 634 +1
============================================
+ Hits 5720 5732 +12
+ Misses 353 338 -15
- Partials 156 162 +6 ☔ View full report in Codecov by Sentry. |
604b3b3
to
58aa282
Compare
@@ -400,4 +400,9 @@ public final <B extends ProviderRegistration> B getFaker() { | |||
public static Method getMethod(AbstractProvider<?> ap, String methodName) { | |||
return ap == null ? null : ObjectMethods.getMethodByName(ap, methodName); | |||
} | |||
|
|||
@Override | |||
public String toString() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't this the default toString?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost. Except that it's shorter due to getSimpleName
instead of getName
.
I would like to make it even more shorter, but it doesn't have any human-readable fields...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but why do you need it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In debug logs.
To make output of such logs a bit better readable (than the default):
LOG.fine(() -> "resExp(%s [%s]) current: %s, root: %s, context: %s, regExpContext: %s -> res: %s".formatted(directive, Arrays.toString(args), current, root, context, regExpContext, res));
@@ -158,8 +158,7 @@ private String generateBase64RasterImage(ImageType imageType, int width, int hei | |||
byte[] imageBytes = baos.toByteArray(); | |||
return "data:" + imageType.mimeType + ";base64," + Base64.getEncoder().encodeToString(imageBytes); | |||
} catch (IOException e) { | |||
e.printStackTrace(); | |||
return null; | |||
throw new RuntimeException("Failed to generate image %s of size %sx%s".formatted(imageType, width, height), e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why you'd want to throw an exception here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it's much better to clearly show the problem instead of hiding it.
- When you return null, it almost always causes NullPointerException in the next step. And user will not understand what happened. For example, in DF own test:
assertThat(faker.image().base64TIFF()).startsWith("data:image/tiff;base64,");
- If user wants to protect, he will need to write IF:
String image = faker.image().base64TIFF();
if (image == null) {
// WHAT SHOULD I DO HERE?..
throw new RuntimeException("WTF, the image is null, and I have no idea why.");
}
Here is more about "Throw Early, Catch Late" Principle
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is writing that if(null) check different from:
try {
String image = faker.image().base64TIFF();
} catch(Exception e) {
throw new RuntimeException("WTF, the image generation failed");
}
I don't mind throwing exceptions early if the code is in my control, but this is a library, and how would a user know this code can throw an exception?
The implicit contract in DF is that none of the methods will ever throw an exception,this changes that approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It differs a lot.
- User doesn't need to check if image is null.
- User doesn't need to pollute his code with unneeded IFs and try/catches.
- User will see a clear error message explaining WHY the image could not be generated.
The implicit contract in DF is that none of the methods will ever throw an exception
It sounds very strange to me.
Nobody can ever guarantee that his code never throws any exceptions.
Anyway, in this case, such an exception actually cannot happen. We need to catch IOException
only because java interfaces like InputStream
declare throws IOException
. But in this case we operate with ByteArrayOutputStream
which never throws IOException
.
} catch (UnknownHostException e) { | ||
return "127.0.0.1"; | ||
} | ||
return getIpV4Address().getHostAddress(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is ideal, I believe the code can throw exceptions now sometimes, and I don't think we should.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also had a doubt, but no, this code doesn't fail.
This is a test that tries through all possible values:
@Test
void allPossibleAddresses() {
for (int first = 2; first < 256; first++) {
for (int second = 2; second < 256; second++) {
for (int third = 2; third < 256; third++) {
for (int fourth = 2; fourth < 256; fourth++) {
InetAddress inetAddress = inet4Address((byte) first, (byte) second, (byte) third, (byte) fourth);
assertThat(inetAddress).isNotNull();
}
}
}
}
}
} catch (UnknownHostException e) { | ||
return "127.0.0.1"; | ||
} | ||
return getPrivateIpV4Address().getHostAddress(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again, I don't think this is correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above: it's correct :)
} catch (UnknownHostException e) { | ||
return "0:0:0:0:0:0:0:1"; | ||
} | ||
return getIpV6Address().getHostAddress(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above. :)
@bodiam Let me summarize: I am pretty sure it's a good PR and should be merged. ;) |
Then feel free to merge it! :) |
…olver) it allows to log what objects were returned when debugging flaky test failures.
... to make it easier to read these values in logs
... especially with FINE level (== DEBUG). This pattern causes flaky failures that are very hard to debug. If something went wrong, it's always safer to throw it with a clear description of what happened. See "Throw early, catch late" principle.
…fore() It was WTF moment when I realized that method BaseFakerTest.before() resets all logger levels to INFO. It took few hours to find out why my logging configuration doesn't work. :(
* by default, FINE logs are NOT written to console, BUT * if some test failed, all its FINE logs are written to console. Thus we can investigate flaky tests.
there is no need to initialize static Faker's vars in some specific way. They should stably work in any order.
b01d682
to
5a81bbf
Compare
This PR doesn't fix #1306, but makes much easier debugging such problems.