google / guava

Google core libraries for Java

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Strings.lenientFormat / Preconditions: Handling of attempts to use non-%s specifiers could perhaps be handled better

captainbkarthick opened this issue · comments

public static String lenientFormat(

The method lenientFormat(String template, Object... args) has been called from many methods in the Preconditions.java class with different data type inputs as arguments.

This method internally converts all the arguments passed as varargs to String. Refer:

int placeholderStart = template.indexOf("%s", templateStart);

Though the method converts the arguments to string, the template String having other data type notations (eg., %d, %f etc.,) are missed to be converted.

This cause a wrong formatted text to be returned.

Possible Solution:
Create a method to replace the string %d/%f/... with %s
or
Implement System.out.printf() logic to format the string.

This is actually intentional behavior, for performance and code size reasons. String.format() is actually quite complex. Also, our core philosophy toward Preconditions has always been that whenever it's not quite right for your needs, it's 100% fine and good to revert to the if/throw idiom. Preconditions is doing its job by making that need a lot less common.

@kevinb9n
The below code will not return the desired data
Preconditions.checkArgument(false, "Port %d of %s must be a positive number", 8080, "Data");

Ideally the above should return
Port 8080 of Data must be a positive number
Whereas the code will return
Port %d of 8080 must be a positive number [Data]

I feel that this is an issue which must be addressed.
The code replaces only the %s with the passed arguments.

int placeholderStart = template.indexOf("%s", templateStart);

Ah I see! What I said before still stands, but you do raise a valid point here. The fact that the 8080 appears in the wrong place can be misleading.

There could be something simple we could do, without bloating code size, that makes it significantly more likely that the values come out in the right place. To give one example, it could:

  • When the number of %s matches the number of args given, behave as today
  • When it does not, check if the count of % matches the number of args; if so, assume that each represents an "attempt", and thereby make the correct matching between valid placeholders and arguments. The others would still appear at the end in brackets as they currently do.

@captainbkarthick You may like to use Error Prone in your compilation. Two Checkers will catch this kind of bugs:

[ERROR] (...) error: [LenientFormatStringValidation] Expected 1 positional arguments, but saw 2
[ERROR] (see https://errorprone.info/bugpattern/LenientFormatStringValidation)
[ERROR] Did you mean 'Preconditions.checkArgument(false, "Port %d of %s must be a positive number (%s)", 8080, "Data");'?

[ERROR] (...) error: [PreconditionsInvalidPlaceholder] Preconditions only accepts the %s placeholder in error message strings
[ERROR] (see https://errorprone.info/bugpattern/PreconditionsInvalidPlaceholder)
[ERROR] Did you mean 'Preconditions.checkArgument(false, "Port %s of %s must be a positive number", 8080, "Data");'?

Thank you @perceptron8. Will take a look at this.

@kevinb9n ,
I am trying multiple solutions to fix this without bloating the code size.
Will keep you posted on this.

Thanks.

@cgdecker / @kevinb9n ,
I have raised a PR. : #6221
Requesting to check.

We can't look at the code until you sign the CLA, but that's okay: what we need now is discussion of the right solution and whether it is worth changing. Feel free to participate in that discussion if you like. As my comment said, "There could be something simple we could do", and "To give one example, it could...". More discussion is needed. This library's been in popular usage for well over a decade so yeah -- changes to it don't happen too quickly!

@kevinb9n,
Apologies!
I closed the issue by mistake and reopened it.

FYI,
I have already signed CLA today. Ref https://github.com/google/guava/pull/6221/checks
Also requesting to share the details of the discussion so that I can participate.

Thanks

The team is quite unconvinced that solving this problem is important enough to change anything for. Note that this is the first time we're hearing about this problem after more than 10 years of wide usage.

In these situations, the main thing that should really happen is for the user to fix their code.

Unfortunately, accepting any change can always result in a cascading series of ongoing tasks, as we then find out that we introduced a bug, or start debating variations of the behavior. For the time being we aren't convinced it's worth it to open the door to that.