Prefer Conciseness over Terseness - No Fluff Just Stuff

Prefer Conciseness over Terseness

Posted by: Venkat Subramaniam on September 7, 2008

As programmers, we prefer languages, frameworks, and APIs that allow us to express ourselves
in fewer lines of code than in several lines of code. We have sung the praises for those that promote
fluency and ease.

However, aiming for fewer lines of code is not a good thing always. The question to
ask is, if the code is merely terse or is it concise.

Terse code, just like concise code, is small. But, terse code is treacherous.
It can easily elude you by either making it hard to follow and/or make it incorrect.

Consider the following Java/JUnit test example (you can use similar example with C# and NUnit
as well—the problem is the same).

@Test
public void convertCtoF()
{
  _converter.from(TemperatureUnit.C);
  _converter.to(TemperatureUnit.F);
  Assert.assertEquals(50.0, _converter.convert(10), 0);
}

In JUnit 4.0, instead of declaring public void testConvertCtoF()you are able to use annotation to indicate that
ConvertCtoF()
 is a test method. JUnit 4.0 promotes conciseness—you did not have to inherit the test class from
a TestCase base class and that is good. The above code tests the convert() method of a TemperatureConverter class.

Now, suppose I want to write an exception test to indicate that the convert() method of
the TemperatureConverter will fail if input value if too large. Here is test for that:

@Test
public void convertCtoFFailureForLargeValue()
{
  _converter.from(TemperatureUnit.C);
  _converter.to(TemperatureUnit.F);
  try
  {
    _converter.convert(Double.MAX_VALUE);
    Assert.fail("Expected exception due to overflow");
  }
  catch(ConverterException ex)
  {
    Assert.assertTrue(true); // :) Success
  }
}

The above exception test passes because the expected exception was thrown by the convert() method.

As an astute reader familiar with JUnit 4.0, you'd point out that I have not taken advantage of expressive
syntax to perform exception test. Rather than writing the clumsy try/catch block with a call to the fail()
method, I should've used the annotation that indicates the desired exception? Let's see how that would look:

@Test(expected=ConverterException.class)
public void convertCtoFFailureForLargeValueLite()
{
   _converter.from(TemperatureUnit.C);
  _converter.to(TemperatureUnit.C);
  _converter.convert(Double.MAX_VALUE);
}

If you run this test, just like the previous test, this test would pass as well since the
ConverterException was thrown. The code size is certainly smaller, but this is an example
of terse code and it is wrong—it is not equivalent to the previous longer version. Sadly,
JUnit 4.0 also promotes terseness.

This example illustrates why I am not a big fan of annotation based exception testing and
why I consider it to be terse rather than being concise.
                    
Even though the test passed, it passed for the wrong reason. It passed because the method
to() threw the exception in this case (because I inadvertently set the same unit for conversion
for from and to), and the test did not even reach the convert()method.

@Test(expected=ConverterException.class) is terse and lacks the granularity that the try/catch block
provides. It does not tell you which method within the test actually threw the exception you're
expecting. This is the same problem with the [ExpectedException(...)] attribute in C# (NUnit and
VS testing tool)—it is terse and lacks graularity.

If you are using Java or C# to test your code, I say the longer verbose version is actually better
than the terse version.

If you are using Groovy or JRuby to test your Java code (or plan to use IronRuby to test
your C# code), you can enjoy a concise syntax to write this test.

Here is a Groovy example to test the TemperatureConverter:

void testConvertCtoFFailureForLargeValueConcise()
{
  _converter.from(TemperatureUnit.C);
  _converter.to(TemperatureUnit.F);
  shouldFail {_converter.convert(Double.MAX_VALUE);}
}

The call to convert() method is placed in a closure block that is provided as parameter to the
shouldFail() method. If the from() or the to() method were to thrown an exception, the test will
fail. Also, if the convert() method does not throw an exception, the test will fail. The way this test
is written, you are expecting the convert() method to throw an exception. You can be precise about
which exception you expect as follows:

shouldFail ConverterException, {_converter.convert(Double.MAX_VALUE);}

The shouldFail() method can take two parameters, the first parameter is the name/type of the
exception it expects, and the second is the closure block that contains the code that is expected to
throw the mentioned exception.

While the annotation based exception testing illustrates terseness, the closure based approach
shows conciseness. Both let you type fewer lines of code, but one is elusive while the other is expressive.
When you come across APIs, evaluate whether they are terse or concise. Prefer conciseness over terseness.
Venkat Subramaniam

About Venkat Subramaniam

Dr. Venkat Subramaniam is an award-winning author, founder of Agile Developer, Inc., creator of agilelearner.com, and an instructional professor at the University of Houston.

He has trained and mentored thousands of software developers in the US, Canada, Europe, and Asia, and is a regularly-invited speaker at several international conferences. Venkat helps his clients effectively apply and succeed with sustainable agile practices on their software projects.

Venkat is a (co)author of multiple technical books, including the 2007 Jolt Productivity award winning book Practices of an Agile Developer. You can find a list of his books at agiledeveloper.com. You can reach him by email at venkats@agiledeveloper.com or on twitter at @venkat_s.

Why Attend the NFJS Tour?

  • » Cutting-Edge Technologies
  • » Agile Practices
  • » Peer Exchange

Current Topics:

  • Languages on the JVM: Scala, Groovy, Clojure
  • Enterprise Java
  • Core Java, Java 8
  • Agility
  • Testing: Geb, Spock, Easyb
  • REST
  • NoSQL: MongoDB, Cassandra
  • Hadoop
  • Spring 4
  • Cloud
  • Automation Tools: Gradle, Git, Jenkins, Sonar
  • HTML5, CSS3, AngularJS, jQuery, Usability
  • Mobile Apps - iPhone and Android
  • More...
Learn More »