DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Improving Unit Test Maintainability
  • 7 Awesome Libraries for Java Unit and Integration Testing
  • Mastering Unit Testing and Test-Driven Development in Java
  • Comprehensive Guide to Unit Testing Spring AOP Aspects

Trending

  • Stateless vs Stateful Stream Processing With Kafka Streams and Apache Flink
  • Immutable Secrets Management: A Zero-Trust Approach to Sensitive Data in Containers
  • Google Cloud Document AI Basics
  • Emerging Data Architectures: The Future of Data Management
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Testing, Tools, and Frameworks
  4. Defensive Programming With Klojang Check

Defensive Programming With Klojang Check

Defensive Programming is a noble goal in concept. Yet, there is surprisingly little in the way of practical, programmatic support.

By 
Ayco Holleman user avatar
Ayco Holleman
·
Aug. 17, 23 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
4.3K Views

Join the DZone community and get the full member experience.

Join For Free

Unit testing and integration testing have long since become established practices. Practically all Java programmers know how to write unit tests with JUnit. IDEs will help you with it and build tools like Maven and Gradle and run them as a matter of course.

The same cannot be said of its (sort of) runtime counterpart: Defensive Programming — ensuring your program or method starts with a clean and workable set of inputs before continuing with the business logic. Null checks are the most common example of this. Yet it often seems like everything beyond that is treated as part of the business logic, even when it arguably isn't. If a method that calculates a price needs some value from a configuration file, is the presence of the configuration file part of the business logic? Probably not, but it should be checked nonetheless.

This is where Klojang Check steps in. Klojang Check is a small Java library that enables you to separate precondition validation and business logic in a clean, elegant, and concise manner. Its take on precondition validation is rather different from, for example, Guava's Preconditions class or Apache's Validate class. It provides a set of syntactical constructs that make it easy to specify checks on program input, object state, method arguments, variables, etc. In addition, it comes with a set of common checks on values of various types. These checks are associated with short, informative error messages, so you don't have to invent them yourselves.

Here is an example of Klojang Check in action:

Java
 
public class InteriorDesigner {

  private final int numChairs;

  public InteriorDesigner(int numChairs) {
    this.numChairs = Check.that(numChairs)
          .is(gt(), 0)
          .is(lte(), 4)
          .is(even())
          .ok();
  }

  public void applyColors(List<Color> colors) {
    Check.that(colors).is(notEmpty().and(contains(), noneOf(), RED, BLUE, PINK));
    // apply the colors ...
  }

  public void addCouch(Couch couch) {
    Check.that(couch).isNot(Couch::isExpensive, ExpensiveCouchException::new);
    // add the couch ...
  }

}


Performance

No one is going to use a library just to check things that aren't even related to their business logic if it is going to hog their CPU. Klojang Check incurs practically zero overhead. That's because it doesn't really do stuff. As mentioned, it only provides a set of syntactical constructs that make precondition validation more concise. Of course, if a value needs to be in a Map before it even makes sense to continue with the rest of a computation, you will have to do the lookup. There are no two ways around it. Klojang Check just lets you express this fact more clearly:

Java
 
Check.that(value).is(keyIn(), map);


If you are interested, you can find the results of the JMH benchmarks here.

Getting Started

To start using Klojang Check, add the following dependency to your POM file:

XML
 
<dependency>
    <groupId>org.klojang</groupId>
    <artifactId>klojang-check</artifactId>
    <version>2.1.3</version>
</dependency>


Or Gradle script:

Plain Text
 
implementation group: 'org.klojang', name: 'klojang-check', version: '2.1.3'


The Javadocs for Klojang Check can be found here.

Common Checks

The CommonChecks class is a grab bag of common checks on arguments, fields (a.k.a. state), and other types of program input. Here are some examples:

Java
 
import static org.klojang.check.CommonChecks.*;

Check.that(length).is(gte(), 0);
Check.that(divisor).isNot(zero());
Check.that(file).is(writable());
Check.that(firstName).is(substringOf(), fullName);
Check.that(i).is(indexOf(), list);


Think of all the if statements it would have taken to hand-code these checks!

Testing Argument Properties

With Klojang Check, you can test not just arguments but also argument properties.
To do this, provide a Function that extracts the value to be tested from the argument.

Java
 
Check.that(fullName).has(String::length, lte(), 100);


The CommonProperties class contains some useful functions that can make your life easier:

Java
 
import static org.klojang.check.CommonProperties.strlen;
import static org.klojang.check.CommonProperties.type;
import static org.klojang.check.CommonProperties.abs;

Check.that(fullName).has(strlen(), lte(), 100);
Check.that(foo).has(type(), instanceOf(), InputStream.class);
Check.that(angle).has(abs(), lte(), 90);


As the last example illustrates, the word "property" needs to be taken in the broadest sense here. These are really just functions that are passed the argument and return the value to be tested.

Providing A Custom Error Message

Klojang Check generates a short, informative error message if the input value fails
a test:

Java
 
Check.that(length).is(gte(), 0);
// error message: argument must be >= 0 (was -42)


But you can provide your own error message if you prefer:

Java
 
Check.that(fullName).has(strlen(), lte(), 100, "full name must not exceed 100 characters");


The message may itself contain message arguments:

Java
 
Check.that(fullName).has(strlen(), lte(), maxLength, 
      "full name must not exceed ${0} characters (was ${1})",
      maxLength
      fullName.length());


There are a few predefined message arguments that you can use in your error message:

Java
 
Check.that(fullName).has(strlen(), lte(), maxLength, 
      "full name must not exceed ${obj} characters (was ${arg})");


This code snippet is exactly equivalent to the previous one, but this time you didn't have to provide any message arguments yourself! ${arg} is the value you are testing, while ${obj} is the value you are testing it against. The reason the latter message argument is called ${obj} is that it is the object of the less-than-or-equal-to relationship, while the argument is used as the subject of that relationship. (For more information, see here.)

Throwing A Custom Exception

By default, Klojang Check will throw an IllegalArgumentException if the input
value fails any of the checks following Check.that(...). This can be customized in two ways:

  1. By providing a function that takes a string (the error message) and returns the exception to be thrown;
  2. By providing a supplier that supplies the exception to be thrown.

Here is an example for each of the two options:

Java
 
// Error message "stale connection" is passed to the constructor of IllegalStateException:
Check.on(IllegalStateException::new, connection.isOpen()).is(yes(), "stale connection");
Check.that(connection.isOpen()).is(yes(), () -> new IllegalStateException("stale connection"));


The CommonExceptions class contains exception factories for some common exceptions:

Java
 
import static org.klojang.check.CommonExceptions.STATE;
import static org.klojang.check.CommonExceptions.illegalState;

Check.on(STATE, connection.isOpen()).is(yes(), "stale connection");
Check.that(connection.isOpen()).is(yes(), illegalState("stale connection"));


Combining Checks

Sometimes you will want to do tests of form x must be either A or B or of the form either x must be A or y must be B:

Java
 
Check.that(collection).is(empty().or(contains(), "FOO"));
Check.that(collection1).is(empty().or(collection2, contains(), "FOO"));


The latter example nicely maintains the Klojang Check idiom, but if you prefer your code with less syntactical sugar, you can also just write:

Java
 
Check.that(collection1).is(empty().or(collection2.contains("FOO"));


When combining checks, you can also employ quantifiers:

Java
 
import static org.klojang.check.relation.Quantifier.noneOf;
import static org.klojang.check.CommonChecks.notEmpty;
import static org.klojang.check.CommonChecks.contains;

Check.that(collection).is(notEmpty().and(contains(), noneOf(), "FOO", "BAR");


Conclusion

We hope this article has given you a flavor of how you can use Klojang Check to much more systematically separate precondition validation from business logic. Klojang Check's conciseness hopefully lowers the bar significantly to just list everything that should be the case for your program or method to continue normally — and write a check for it!

Defensive programming Integration testing Java (programming language) unit test

Opinions expressed by DZone contributors are their own.

Related

  • Improving Unit Test Maintainability
  • 7 Awesome Libraries for Java Unit and Integration Testing
  • Mastering Unit Testing and Test-Driven Development in Java
  • Comprehensive Guide to Unit Testing Spring AOP Aspects

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!