In the world of software development, maintaining clean, efficient, and error-free code is a constant challenge. Tools like Refaster and OpenRewrite are designed to help developers automate refactoring and eliminate technical debt. Here, we explore how these tools function, their integrations with Error Prone, and their unique strengths.

Error Prone

Google Error Prone

Error Prone (from Google) is a static analysis tool for Java that identifies common programming mistakes during compile-time. With over 500 built-in bug checks, it’s a robust resource for developers aiming to write more reliable and maintainable Java code. Although it supports Refaster templates, it does not come with any Refaster rules. While Error Prone’s support of Refaster rules is limited to statements and expressions, it is still very powerful.

Picnic’s Error Prone Support

Error Prone Support is an extension of Google’s Error Prone made (and opinionated) by Picnic.

“It aims to improve code quality, focussing on maintainability, consistency and avoidance of common pitfalls.”

It comes with 900+ Refaster rules and 40+ Error Prone bug checks.

Refaster

Refaster is a powerful tool for automating refactoring in Java codebases. It works by defining patterns of code to find and replace using Refaster templates, which act as blueprints for these transformations.

Refaster Templates

Refaster template is any class with multiple methods with the same return type and list of arguments with the same name. One of the methods should be annotated @AfterTemplate, and every other method should be annotated with @BeforeTemplate. Annotation @BeforeTemplate identifies the code pattern to replace, while @AfterTemplate defines the desired code transformation - what the code should look like after transformation.

There are also more advanced options, e.g.:

  • @AlsoNegation handles transformations involving negated expressions,
  • you may use multiple @BeforeTemplate annotations,
  • define complex patterns using constructs like Refaster.anyOf(<expr1>, <expr2>, ...), and
  • @UseImportPolicy specifies how imports should be managed during refactoring.

Anatomy of Refaster rule

import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.AlsoNegation;

class Utf8Length {     // A name for the refactoring
  @BeforeTemplate      // This is what the code looks like before the refactoring
  int toUtf8Length(    // the method name is unimportant
      String string /* the string parameter stands in for any expression of type String */) {
    return /* this is here just to make the compiler happy */
        string.getBytes(StandardCharsets.UTF_8).length;
        // this is what the code looks like before the refactoring
  }

  @AfterTemplate    // replace code with this pattern
  int optimizedMethod(
      String string /* substitute in the original String expression */) {
    return Utf8.encodedLength(string);
  }
}

This results in the following code change:

int testUtf8EncodedLength() {
-  return "foo".getBytes(UTF_8).length;
+  return Utf8.encodedLength("foo");
}

Refaster rule example

import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.AlsoNegation;
import com.google.errorprone.refaster.annotation.BeforeTemplate;

public class StringIsEmpty {
  @BeforeTemplate
  boolean equalsEmptyString(String string) {
    return string.equals("");
  }

  @BeforeTemplate
  boolean lengthEquals0(String string) {
    return string.length() == 0;
  }

  @AfterTemplate
  @AlsoNegation
  boolean optimizedMethod(String string) {
    return string.isEmpty();
  }
}

This results in the following code change:

Set<Boolean> testStringIsEmpty() {
   return ImmutableSet.of(
-      "foo".length() == 0,
-      "bar".length() <= 0,
-      "baz".length() < 1,
-      "foo".length() != 0,
-      "bar".length() > 0,
-      "baz".length() >= 1);
+      "foo".isEmpty(),
+      "bar".isEmpty(),
+      "baz".isEmpty(),
+      !"foo".isEmpty(),
+      !"bar".isEmpty(),
+      !"baz".isEmpty());
}

OpenRewrite

OpenRewrite, developed by Moderne, offers an advanced automated refactoring ecosystem. It supports multiple programming languages and technologies, making it a versatile tool for eliminating technical debt across diverse codebases.

OpenRewrite supports three types of recipes:

  • Declarative recipes: Simplify transformations with declarative logic.
  • Refaster template recipes: Adapt Refaster’s capabilities to OpenRewrite’s ecosystem.
  • Imperative recipes: Allow complex, programmatic refactoring for advanced use cases.

OpenRewrite’s recipes can analyze and refactor across multiple files, ensuring consistency and thoroughness. It maintains the original style of the code, reducing the cognitive load for developers reviewing changes.

Error Prone vs OpenRewrite

Error Prone and OpenRewrite have some key differences presented in the table below.

FeatureError ProneOpenRewrite
Build IntegrationIntegrated into the build processCan be integrated into the build, complex and slow
Language SupportJava (17+) onlyMultiple languages & technologies
Refaster FeaturesFull supportPartial support
Advanced RefactoringLimitedSupports advanced techniques
PerformanceFastThorough but slow
Cross-File RefactoringNoYes
Style PreservationNoYes

It is difficult to say which tool, Error Prone or OpenRewrite, is better. They both have some advantages and disadvantages. Setting up Error Prone with Refaster rules is more simple, it can be directly integrated into the build, it is fast, but the refactoring is limited to Refaster templates and it only supports Java 17+. Hopefully, you or your team are using Java 21+. OpenRewrite supports advanced refactoring techniques, but its learning curve is much steeper. It can be integrated into the build process but is complex and slow. It supports multiple languages and technologies and offers cross-file refactoring and preserves style, which might be important during code review.

When selecting the tool to help you with refactoring, it would be best to weigh pros and cons of both tools and consider your company’s needs and use the right tool for the job.