Path: csiph.com!aioe.org!.POSTED!not-for-mail From: =?UTF-8?Q?Arne_Vajh=c3=b8j?= Newsgroups: comp.lang.java.programmer Subject: Re: How to handle same sanity checks Date: Sun, 25 Jun 2017 22:55:55 -0400 Organization: Aioe.org NNTP Server Lines: 254 Message-ID: References: <87o9tgo0a9.fsf@munus.decebal.nl> <87fuernwav.fsf@munus.decebal.nl> NNTP-Posting-Host: PBuS014lhzVyHjLbXmo/ZQ.user.gioia.aioe.org Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 7bit X-Complaints-To: abuse@aioe.org User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Thunderbird/52.2.1 Content-Language: en-US X-Notice: Filtered by postfilter v. 0.8.2 Xref: csiph.com comp.lang.java.programmer:36725 On 6/23/2017 12:48 AM, Cecil Westerhof wrote: > On Thursday 22 Jun 2017 20:33 CEST, Volker Borchert wrote: >> Eric Sosman wrote: >>> On 6/22/2017 5:10 AM, Cecil Westerhof wrote: >>>> In my code I have several methods that start with: >>>> if ((n < MIN_VAL) || (n > MAX_VAL)) { >>>> throw new InvalidParameterException(String.format(ERROR_N, n)); >>>> } >>>> >>>> That is not really DRY. >>>> >>>> I could write a method like: >>>> private void checkN(n) { >>>> if ((n < MIN_VAL) || (n > MAX_VAL)) { >>>> throw new InvalidParameterException(String.format(ERROR_N, n)); >>>> } >>>> } >>>> >>>> And then replace all the checks with a call to checkN. But then >>>> the exception is generated in the method checkN instead of the >>>> method that has a wrong parameter. What is the best way to handle >>>> this? >>> >>> Tastes surely vary, but for repetitive checks I favor writing the >>> private internal helper method. Yes, the caller of doItPlease() >>> gets an exception originating from internalSanityCheck() instead, >>> but that shouldn't bother anyone. doItPlease() will be in the stack >>> trace anyhow, even though it's not at the very top. >> >> That can be a problem with some more-or-less-broken logging >> frameworks, and a nuisance if trying to diagnose over the phone. >> >> You can always do something like >> >> final InvalidParameterException e >> = new InvalidParameterException(String.format(ERROR_N, n)); >> final StackTraceElement[] s = e.getStackTrace(); >> e.setStackTrace(Arrays.copyOfRange(s, 1, s.length)); >> throw e; > > That is a very good idea. I have now: > private static void checkInRange(final int n, > final int lowLimit, > final int highLimit, > final String message) { > if ((n < lowLimit) || (n > highLimit)) { > final InvalidParameterException e = > new InvalidParameterException( > String.format(message, lowLimit, highLimit, n)); > final StackTraceElement[] s = e.getStackTrace(); > e.setStackTrace(Arrays.copyOfRange(s, 1, s.length)); > throw e; > } > } > > Works like a charm. > The code is now more clear and easier to change, while the stack trace > gives the right information. That is probably as far as you can get in pure Java. If you are willing to use AspectJ with annotations there is another way. Demo: package paramcheck; import java.util.function.Consumer; public class TestMain { @CheckParams public static void test1(@IntIntervalCheck(minValue=1,maxValue=10) int v) { System.out.println(v + " is OK"); } @CheckParams public static void test2(@StringLengthCheck(minLength=1,maxLength=10) @StringPatternCheck(pattern="[A-Z]\\d+") String s) { System.out.println(s + " is OK"); } public static void test(Consumer f, T o) { try { f.accept(o); } catch(ParamException ex) { System.out.println(ex.getMessage()); ex.printStackTrace(); } } public static void main(String[] args) { test((o) -> test1(o), 5); test((o) -> test1(o), 50); test((o) -> test2(o), "A123"); test((o) -> test2(o), "XXXXXXXXXXXXXXX"); test((o) -> test2(o), "ABCD"); } } Output: 5 is OK 50 not in interval paramcheck.ParamException: 50 not in interval at paramcheck.TestMain.test1(TestMain.java:8) at paramcheck.TestMain.lambda$1(TestMain.java:24) at paramcheck.TestMain.test(TestMain.java:16) at paramcheck.TestMain.main(TestMain.java:24) A123 is OK XXXXXXXXXXXXXXX invalid length paramcheck.ParamException: XXXXXXXXXXXXXXX invalid length at paramcheck.TestMain.test2(TestMain.java:12) at paramcheck.TestMain.lambda$3(TestMain.java:26) at paramcheck.TestMain.test(TestMain.java:16) at paramcheck.TestMain.main(TestMain.java:26) ABCD does not match pattern paramcheck.ParamException: ABCD does not match pattern at paramcheck.TestMain.test2(TestMain.java:12) at paramcheck.TestMain.lambda$4(TestMain.java:27) at paramcheck.TestMain.test(TestMain.java:16) at paramcheck.TestMain.main(TestMain.java:27) (see the rest of the code below) I think it is nice code. But it also has its drawbacks - it brings in an entire new paradigm. Arne package paramcheck; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface IntIntervalCheck { public int minValue() default 0; public int maxValue() default Integer.MAX_VALUE; public String message() default "%d not in interval"; } package paramcheck; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface StringLengthCheck { public int minLength() default 0; public int maxLength() default 255; public String message() default "%s invalid length"; } package paramcheck; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface StringPatternCheck { public String pattern() default ".*"; public String message() default "%s does not match pattern"; } package paramcheck; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface CheckParams { } package paramcheck; import java.lang.reflect.Method; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; @Aspect public class CheckParamsAdvice { @Pointcut("execution(@paramcheck.CheckParams * *.*(..))") public void checkParamsPointcut() { } @Before("checkParamsPointcut()") public void checkParams(JoinPoint jp) { try { Method m = ((MethodSignature)jp.getSignature()).getMethod(); Object[] args = jp.getArgs(); for(int i = 0; i < args.length; i++) { ParamsChecker.check(m.getParameterAnnotations()[i], args[i]); } } catch (SecurityException e) { e.printStackTrace(); } } } package paramcheck; import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.regex.Pattern; public class ParamsChecker { public static void throwException(String msg) { ParamException ex = new ParamException(msg); StackTraceElement[] ste = ex.getStackTrace(); ex.setStackTrace(Arrays.copyOfRange(ste, 3, ste.length)); throw ex; } public static void check(Annotation[] ans, Object o) { for(Annotation an : ans) { if(an instanceof IntIntervalCheck) { IntIntervalCheck iic = (IntIntervalCheck)an; int v = (int)o; if(v < iic.minValue() || iic.maxValue() < v) { throwException(String.format(iic.message(), v)); } } if(an instanceof StringLengthCheck) { StringLengthCheck slc = (StringLengthCheck)an; String s = (String)o; if(s.length() < slc.minLength() || slc.maxLength() < s.length()) { throwException(String.format(slc.message(), s)); } } if(an instanceof StringPatternCheck) { StringPatternCheck spc = (StringPatternCheck)an; String s = (String)o; if(!Pattern.matches(spc.pattern(), s)) { throwException(String.format(spc.message(), s)); } } } } } package paramcheck; public class ParamException extends RuntimeException { private static final long serialVersionUID = 1L; public ParamException(String msg) { super(msg); } }