📜 ⬆️ ⬇️

Safety Code: Null Safe Design Pattern

Content


I. Description of the problem
Ii. Review of existing solutions
Iii. Solution without aspects.
Iv. AspectJ solution
V. Dynamic aspects
Vi. Afterword.
VII. References and literature

I.Description of the problem


When programming in Java (and some other languages), it is often necessary to work with string variables and other types that are wrapper objects of primitive types, for example: Boolean, Integer, Double. For example, there is a bin (hereinafter, a bin means a class that supports the recommendations of Java Beans):
public class SomeBean{ private String strField; public void setStrField(String strField){ this.strField=strField; } public String getStrField(){ return strField; } //    } 

Further, when using this bean, we must check the field for null:
 String s=someBean.getStrField(); if (s!=null){ if (!s.isEmpty()){ // s.length()>0 Java 5- doSomeThing(); } } 

And so to do with any field object. If you do not, then sooner or later the application will end with an unchecked NullPointerException. You can, of course, forcibly intercept this exception by transferring your errors to the system, however this is considered a sign of an illiterate programming style and entails a loss of performance, since the generation of an exception is equivalent to dozens of regular commands. Such a check for null has to be done repeatedly, in the bin itself and in many places outside of it. The more often a bin is used, and the harder an application is, the more often you have to check for null, and therefore you have to perform routine work, which makes you likely to miss something, and this increases the likelihood of the application not working. This approach confuses the code, makes it less intuitive, leads to an increase in redundant code and reduces speed. As a programmer, I often come across such things from colleagues and even in my practice. Next, a brief overview of the existing means of dealing with null-dependency, and an effective, in my opinion, general solution based on the application of aspects will be presented.

Ii. Review of existing solutions


I assumed that someone had already done it before me, and made a review of existing solutions.

1) @NotNull annotation from JSR-305 is described in detail in [1] and [2].
It is placed in front of fields and methods that return objects. Allows you to determine at the source level pre-marked IDE problem areas.
 @NotNull public Color notNullMethod(){ return null; } 

Disadvantage: the annotation itself does not solve the problem, and you need to remember to put it in all problem areas, JSR-305 is not able to analyze complex code.
')
2) Checker Framework JSR-308 is described in [3] and [4].
Represents a more advanced technique compared to the JSR-305 by introducing additional code checks to analyze more complex code when checking for null, and also provides many other useful annotations for multitasking checks, types, regular expressions, and others.
Advantages: developed framework
Disadvantages in relation to the problem under discussion: the @NotNull annotation does not represent a solution; you need to remember to annotate in all problem areas.

3) The Java Annotation Checker (JACK) [5] has the same strengths and weaknesses as JSR-305

4) Project Coin, which contains many recommendations for improving Java (influenced by the Groovy language), is more detailed in [6] and [7]. Consider an example of a function:
 public String getPostcode(Person person){ if (person != null){ Address address = person.getAddress(); if (address != null){ return address.getPostcode(); } } return null; } 

At the suggestion of Project Coin, this function could be rewritten via NullSafe-navigation:
 public String getPostcode(Person person){ return person?.getAddress()?.getPostcode(); } 

Unfortunately, Oracle did not include this feature in Java 7, so Java will have to be content with other solutions.

5) Application language Groovy. Actually, the NullSafe-navigation described above went from there [8].
Virtue: a significant simplification of the code
Disadvantage: you need to remember all the problem areas anyway

6) Apache Commons, StringUtils [9] and PropertyUtils [10]:
 StringUtils.isBlank(null)= true StringUtils.isBlank("")= true StringUtils.isBlank(" ")= true String firstName = (String) PropertyUtils.getSimpleProperty(employee, "firstName"); String city = (String)PropertyUtils.getNestedProperty(employee, "address(home).city"); 

Advantages: simplified checks
Disadvantages: need to check wherever required. Additional code is needed to handle exceptions generated by PropertyUtils.

7) Analyzers of static code FindBugs [11] and PMD [12], which have plugins for most IDEs.
Advantages: Very powerful and useful tools that analyze static code without prior injection of annotations.
Disadvantages: They show problem areas, but do not provide a ready-made general solution.

Iii. Solution without aspects


Checks for null, like any data reading task, are more common in practice than assigning (writing) values. Therefore, the read operation should be extremely optimized for performance. You can rewrite the interaction with the bin fields in this case:
 public class SomeBean{ private String strField=""; public void setStrField(String strField){ if (strField!=null){ this.strField=strField; } } public String getStrField(){ return strField; } } 

Null values ​​are then ignored. Also fields are forcibly initialized. All this together allows you to create a null-safe code, in which you no longer need to perform numerous null checks, which greatly simplifies writing the code and drastically reduces the chance of something missing.
Advantages: there is no need to worry about null checking to work with such fields, which greatly simplifies the code and increases speed.
Disadvantages: for each field you have to write initialization code and null cut code.

Iv. AspectJ solution


In order not to write for each code, null clipping is simply asking for a solution with the help of aspects that will provide end-to-end functionality. Obviously, it is desirable to act at the field level, since operations with fields can be inside a bin, and the end-to-end functionality should be universal, otherwise it would not make sense.
For selective tagging of fields and entire classes, we introduce the NullSafe annotation:
 @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) public @interface NullSafe { boolean getter() default true; } 

  @NullSafe (getter = false) 
- it is necessary to simplify the logic and increase the speed if the programmer is not too lazy to initialize the fields in the bin, in this case only the field entry is subject to the aspect.
For a future easy modification, the aspect was written using annotated AspectJ 5 and requires Java 5+ to work. Details about aspects can be found in [13], [14], [15].
 @Aspect public class NullSafeAspect { @Pointcut(value="within(@NullSafe *)") // support annotation by class public void byClass() {} @Pointcut(value="!within(@NullSafe(getter=false) *)") public void byClassNoGetter() {} @Pointcut(value="get(@NullSafe Long||Integer||Double||Float||Short||Byte||java.math.BigDecimal *)") public void fieldGetNumerics() {} @Pointcut(value="!get(@NullSafe(getter=false) * *)") public void fieldNoGetter() {} @Pointcut(value="get(Long||Integer||Double||Float||Short||Byte||java.math.BigDecimal *)") public void classGetNumerics() {} @Pointcut(value="set(Long||Integer||Double||Float||Short||Byte||java.math.BigDecimal *)") public void classSetNumerics() {} @Pointcut(value="set(@NullSafe * *)") // all field-annotated public void fieldSetAllSupported() {} @Pointcut(value="classSetNumerics() || set(String||Boolean||Character *)") public void classSetAllSupported() {} @Pointcut(value="fieldGetNumerics() || get(@NullSafe String||Boolean||Character *)") public void fieldGetAllSupported() {} @Pointcut(value="classGetNumerics() || get(String||Boolean||Character *)") public void classGetAllSupported() {} @Around(value="(fieldSetAllSupported() || byClass() && classSetAllSupported()) && args(v)") public void aroundSet(ProceedingJoinPoint jp, Object v) throws Throwable{ toLogSetter(jp, v); if (v!=null){ jp.proceed();} } @Around(value="get(@NullSafe String *) && fieldNoGetter() || byClass() && get(String *) && byClassNoGetter()") public String aroundGetString(ProceedingJoinPoint jp) throws Throwable{ String v=(String)jp.proceed(); if (v==null){return "";} return v; } private Field getField(JoinPoint jp){ try{ Signature sig=jp.getStaticPart().getSignature(); Field field=sig.getDeclaringType().getDeclaredField(sig.getName()); field.setAccessible(true); return field; }catch(Exception e){ } return null; } private Object getBean(JoinPoint jp){ try { Field field=getField(jp); if (field==null){return null;} Object obj=field.getType().newInstance(); field.set(jp.getTarget(),obj); return obj; }catch(Exception e){ stackTraceToLog(e); } return null; } @Around(value="!fieldGetAllSupported() && get(@NullSafe * *) && fieldNoGetter() && byClassNoGetter()") public Object aroundGetBean(ProceedingJoinPoint jp) throws Throwable{ Object v=jp.proceed(); if (v==null){ return getBean(jp); } return v; } private Object getNumeric(JoinPoint jp){ try { Field field=getField(jp); if (field==null){return null;} Object obj=field.getType().getDeclaredConstructor(String.class).newInstance("0"); field.set(jp.getTarget(),obj); return obj; }catch(Exception e){ stackTraceToLog(e); } return null; } @Around(value="fieldGetNumerics() && fieldNoGetter() || byClass() && classGetNumerics() && byClassNoGetter()") public Object aroundGetNumerics(ProceedingJoinPoint jp) throws Throwable{ Object v=jp.proceed(); if (v==null){ return getNumeric(jp); } return v; } } 

An example of applying annotations:
 @NullSafe public class SomeClassTestByClass extends SomeClass { ... } @NullSafe private String strField; @NullSafe private HashMap<Integer,HashMap<String,String>> map; @NullSafe(getter=false) private ArrayList<String> listString=new ArrayList<String>(); 

The following is an explanation of this aspect. The @Pointcut annotation describes the standard intersection points. Within - means an action inside the class, in this case, any class marked with the @ NullSafe annotation. Numeric types are Long, Integer, Double, Float, Short, Byte, BigDecimal, which are initialized with the same pattern. String, Boolean, Character are also supported. All of the wrapper classes mentioned above are included in the concept of types supported by the aspect. You can also annotate at the field level any other bin or class supporting the constructor without parameters. Class annotation implies full support for all supported field types at once. All these fields are initialized using reflection. If you initialize the fields in the code, this will increase the speed by discarding getters using @ NullSafe (getter = false). Set () and get () - at the intersection points are responsible for writing or reading the bin field, including for operations with fields inside the bin. The @Advice tip is responsible for the actions at the intersection points. Using the Before advice to intersect set () did not seem to be a good idea, because an exception is required to prevent execution of the code, which is unacceptable in terms of speed. By default, an aspect is created as a singleton. All processing is provided with only one method in the project for each type. A complete example of the finished aspect with tests is given in the NullSafe project in [18].
Advantage: end-to-end functionality only where it is needed.
Disadvantages: you still need to make annotations at the level of classes and individual fields, and the end-to-end functionality for all classes without restrictions is shown to me by the @ NullSafe annotation as an incorrect solution.

V. Dynamic aspects


Aspects are usually static code modifications and are performed at compile time. Therefore, a complex description of the points of intersection does not affect the speed of the finished program. AspectJ can also dynamically modify bytecode by changing the bootloader. This is necessary in the case of the application of aspects for byte-code for which there is no source code. In this case, you need to remove the annotations and add a reference to certain classes in the NullSafeAspect aspect (or create a new aspect that inherits it). Since this technique is specific to different application servers and environments, I will not dwell on it in detail. More details on the use of dynamic aspects can be found in [16] and [17].

Vi. Afterword


This article is an attempt to look at a well-known problem under a different look. Perhaps the use of null-values ​​is necessary for any particular algorithm or database, but in this case, as my practice shows, it is often doubtful whether the algorithm or the database is optimal, or if they contain unnecessary data. The approach described in the article may well become a standard technique for writing more compact and bug-resistant code, that is, to become one of the design patterns in Java.

VII. References and literature *


1. jcp.org/en/jsr/detail?id=305
2. www.jetbrains.com/idea/documentation/howto.html
3. jcp.org/en/jsr/detail?id=308
4. types.cs.washington.edu/checker-framework/current/checkers-manual.html
5. homepages.ecs.vuw.ac.nz/~djp/JACK
6. blogs.oracle.com/darcy/entry/project_coin_final_five
7. metoojava.wordpress.com/2010/11/15/java-7-awesome-features
8. groovy.codehaus.org/Operators
9. commons.apache.org/lang/api-2.5/org/apache/commons/lang/StringUtils.html
10. commons.apache.org/beanytils/apidocs/org/apache/commons/beanutils/package-summary.html#package_description
11. findbugs.sourceforge.net
12. pmd.sourceforge.net
13. eclipse.org/aspectj/doc/released/adk15notebook/index.html
14. eclipse.org/aspectj/doc/released/progguide/index.html
15. AspectJ in Action. Second Edition. ISBN 978-1-933988-05-4
16. www.eclipse.org/aspectj/doc/next/devguide/ltw.html
17. static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/aop.html#aop-aj-ltw
18. sourceforge.net/projects/nullsafe/files
* All Internet links at the time of this writing were valid.
02/2013 Oleg Nikolaenko

Source: https://habr.com/ru/post/169827/


All Articles