I recently had to create a new simple class. Its purpose was to simply encapsulate a String value, but because it was a business domain value and I needed to be able to find usages later in the code, it made sense to create its own class instead of just using String (that is worth another discussion). It addition to just wrapping the string, there were some other processes that were needed to relating to the value. Mainly that the input from the user could be a list of values and I needed to be able to parse those into the individual values and needed to do that in only one place. So here is the class in Java. I have removed all the blank lines and any comments so I can easily compare it next with the Groovy version.
(Note -- another discussion can be had on the merit of including all the static methods in this class :-))
package test.application;
import java.util.List;
import com.google.common.collect.Lists;
public class ApplicationName {
private final String value;
public ApplicationName(String applicationName) {
this.value = applicationName;
}
public static ApplicationName from(String applicationName) {
return new ApplicationName(applicationName);
}
public static List<ApplicationName> listFrom(String applicationName) {
List<ApplicationName> list = Lists.newArrayList();
String[] split = applicationName.split(",");
for (String s : split) {
list.add(from(s));
}
return list;
}
public static String toString(List<ApplicationName> list) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
if (i > 0) sb.append(",");
sb.append(list.get(i).value);
}
return sb.toString();
}
public List<ApplicationName> asList() {
return listFrom(value);
}
public String getValue() {
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ApplicationName that = (ApplicationName) o;
if (value != null ? !value.equals(that.value) : that.value != null) return false;
return true;
}
@Override
public int hashCode() {
return value != null ? value.hashCode() : 0;
}
}
|
And here is the Groovy version. As you can see it is much shorter and (if you know the groovy syntax) easier to read.
package test.application
import groovy.transform.Canonical
@Canonical
class ApplicationName {
final String value
public static ApplicationName from(String applicationName) {
new ApplicationName(applicationName)
}
public static List<ApplicationName> listFrom(String applicationName) {
String[] split = applicationName.split(",")
split.collect() { name -> from(name) }
}
public static String toString(List<ApplicationName> list) {
list.collect { n -> n.value }.join(',')
}
public List<ApplicationName> asList() {
listFrom(value)
}
}
|
Here are the statistical differences in the two. As you can see, the Groovy code is less than half the Java code size. This is a significant impact on how quickly code can be written as well as reducing "technical debt". Some of the differences are that Groovy will generate the getter for the value. And the @Canonical annotion will generate the constructor, the equals and hashCode methods, and also the toString (which I didn't even include in the Java version, but it would have added even more code to the statistics) – significantly reducing the boiler plate code that needs to be written for Java. Groovy also returns the last value in the method automatically without having to specify "return". And you don't have to use ";" either.
|
Java |
Groovy |
% decrease |
| Lines |
46 |
19 |
58.7% |
| Characters |
1574 |
625 |
60.3% |
In addition, let's look at the Unit test cases written in both Java and Groovy. While not as significant in code reduction, the Groovy code is still easier to write. Also, of note, the Groovy unit test class can be used to test both the Java and Groovy versions of the real class. So even if you have to write the original class in Java, you can still write the unit tests in Groovy and pick up some development time.
package test.application;
import static org.junit.Assert.assertEquals;
import java.util.List;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ApplicationNameTest {
@Test
public void testFrom() {
ApplicationName name1 = ApplicationName.from("test,sam");
ApplicationName name2 = new ApplicationName("test,sam");
assertEquals(name1.getValue(),"test,sam");
assertEquals(name1, name2);
}
@Test
public void testListFrom() {
List<ApplicationName> list = ApplicationName.listFrom("test,sam");
assertEquals(list.get(0).getValue(), "test");
assertEquals(list.get(1).getValue(), "sam");
}
@Test
public void testToString() {
List<ApplicationName> list = ApplicationName.listFrom("test,sam");
assertEquals(ApplicationName.toString(list), "test,sam");
}
@Test
public void testAsList() {
List<ApplicationName> list = ApplicationName.listFrom("test,sam");
ApplicationName name1 = ApplicationName.from("test,sam");
assertEquals(name1.asList(),list);
}
}
|
package test.application
class ApplicationNameTest extends GroovyTestCase {
void testFrom() {
def name1 = ApplicationName.from("test,sam")
def name2 = new ApplicationName("test,sam")
assert name1.value == "test,sam"
assert name1 == name2
}
void testListFrom() {
def name1 = ApplicationName.listFrom("test,sam")
assert name1.value == ["test","sam"]
}
void testToString() {
def name1 = ApplicationName.listFrom("test,sam")
assert ApplicationName.toString(name1) == "test,sam"
}
void testAsList() {
def name1 = ApplicationName.from("test,sam")
assert name1.asList() == [ApplicationName.from("test"), ApplicationName.from("sam")]
}
}
|
Statistics for the unit test classes:
|
Java |
Groovy |
% decrease |
| Lines |
32 |
21 |
34.4% |
| Characters |
1200 |
760 |
36.7% |
So overall for writing both the original classes and the unit test cases, we have:
|
Java |
Groovy |
% decrease |
| Lines |
78 |
40 |
48.7% |
| Characters |
3774 |
1385 |
63.3% |
No comments:
Post a Comment