Hello Nicolas,

Here are a few inline comments regarding the code.

Maybe I overlooked some good reason justifying some design decision you
made, so if you want to discuss more about the suggestions I proposed,
we can do some pair programming next week.

nma...@apache.org writes:

> Author: nmalin
> Date: Fri Aug  9 20:37:11 2019
> New Revision: 1864832
>
> URL: http://svn.apache.org/viewvc?rev=1864832&view=rev
> Log:
> Implemented: Homogenize displaying number with multiple format
> (OFBIZ-7532)
>
> To display a number we had different possibilities :
>  * on ftl use the template <@ofbizAmount and <@ofbizCurrency
>  * by java call a function UtilFormatOut.formatAmount, 
> UtilFormatOut.formatPrice, UtilFormatOut.formatQuantity, etc..
>  * by form widget, use <display type=accounting-number for accounting but 
> nothing for other
>
> To simplify and homogenize all, I implemented a number type purpose :
>    * default: display a number by default, use when no purpose is present
>    * quantity: display a number as a quantity
>    * amount: display a number as an amount (like price without currency)
>    * spelled: litteral displaying for a number (use on <@ofbizAmount ftl only 
> before)
>    * percentage: display a number as a percentage
>    * accounting: diplay a number for accounting specific
>
> Each purpose can be associate to a number for displaying it :
>    * on ftl <@ofbizNumber number=value format=purpose/>
>    * on java UtilFormatOut.formatNumber(value, purpose, delegator, locale)
>    * on form widget <display type=number format=purpose/>
>
> The format use by a purpose is define on 
> framework/common/config/number.properties with the template
>     .displaying.format = ##0.00
>
> With this, you can surchage a configuration, create your own purpose or 
> surchage only one through entity SystemProperty.
>
> Concerning the backware compatibility: 
>  * For the ftl the template <@ofbizAmount is now a link to '<@ofbizNumber 
> format=amount'
>  * For java all previous function call UtilFormatOut.formatNumber with the 
> matching purpose
>  * For form xml accounting-number is managed as an exection
>
> Last point, display a currency is different that a number, so I didn't 
> refactoring some code for this case (only move properties from general to 
> number for centralize de configuration on the same file)
>
> Thanks Charles Steltzlen to start the refactoring
>
[...]

> Modified: 
> ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilFormatOut.java
> URL: 
> http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilFormatOut.java?rev=1864832&r1=1864831&r2=1864832&view=diff
> ==============================================================================
> --- 
> ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilFormatOut.java
>  (original)
> +++ 
> ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilFormatOut.java
>  Fri Aug  9 20:37:11 2019

[...]

> @@ -34,16 +34,11 @@ import java.util.TimeZone;
>  public final class UtilFormatOut {
>  
>      public static final String module = UtilFormatOut.class.getName();
> -
> -    // ------------------- price format handlers -------------------
> -    // FIXME: This is not thread-safe! DecimalFormat is not synchronized.
> -    private static final DecimalFormat priceDecimalFormat = new 
> DecimalFormat(UtilProperties.getPropertyValue("general", 
> "currency.decimal.format", "#,##0.00"));
> -
> -    // ------------------- quantity format handlers -------------------
> -    private static final DecimalFormat quantityDecimalFormat = new 
> DecimalFormat("#,##0.###");
> -
> -    // ------------------- percentage format handlers -------------------
> -    private static final DecimalFormat percentageDecimalFormat = new 
> DecimalFormat("##0.##%");
> +    public static final String DEFAULT_FORMAT = "default";
> +    public static final String AMOUNT_FORMAT = "amount";
> +    public static final String QUANTITY_FORMAT = "quantity";
> +    public static final String PERCENTAGE_FORMAT = "percentage";
> +    public static final String SPELLED_OUT_FORMAT = "spelled-out";

Intuitively an ‘enum’ would seem more appropriate to make the relation
between those constants clearer.

--8<---------------cut here---------------start------------->8---
enum Format {
   DEFAULT, AMOUNT, QUANTITY, PERCENTAGE, SPELLED_OUT;

   // Converts a string to a typed value.
   static Format valueOf(String name) {
      ...   
   }
}
--8<---------------cut here---------------end--------------->8---

Additionally I would be nice if the meaning of those “symbols” could be
documented inside the code either in the XSD with a reference in the
Java code, or in both in XSD and Java.

>  
>      private UtilFormatOut() {}
>  
> @@ -54,43 +49,70 @@ public final class UtilFormatOut {
>          return "";
>      }
>  
> -    /** Formats a Double representing a price into a string
> -     * @param price The price Double to be formatted
> -     * @return A String with the formatted price
> +    /** Format a number with format type define by properties
> +     *

Please expand the javadoc instead of removing it.

>       */
> -    public static String formatPrice(Double price) {
> -        if (price == null) {
> +    public static String formatNumber(Double number, String formatType, 
> Delegator delegator, Locale locale) {

The dependency on the Delegator should be avoided if possible because it
makes writing unit complex tests. By the way where are those tests ? :-)

To remove a dependency on a class which is hard to instantiate like
‘Delegator’, the common solution is to replace it with an interface
(ideally a functional interface to let the caller pass a lambda).

In this particular case after a quick look, the ‘delegator’ argument
could potentially be replaced by a ‘templateProvider’ argument of type
‘Function<String, String>’ where the input is a format type and the
output is a template.

When things are complicated to decouple, an alternative is write an
overload specifically for the test and use it inside the coupled one.

> +        if (number == null) {
>              return "";
>          }
> -        return formatPrice(price.doubleValue());
> +        if (formatType == null) {
> +            formatType = DEFAULT_FORMAT;
> +        }
> +        if (locale == null) {
> +            locale = Locale.getDefault();
> +        }

Does silencing ‘null’ values really make sense here? if ‘null’ values
doesn't have sound meaning, I would recommend bailing out early instead
of silencing a possible programming mistake or worse let another method
downstream throw a cryptic exception later.  To bail out early you can
use things like ‘Objects.requireNonNull(myArg)’.

Have a nice weekend!

-- 
Mathieu Lirzin
GPG: F2A3 8D7E EB2B 6640 5761  070D 0ADE E100 9460 4D37

Reply via email to