Syntactic Sugar Is Not Always Good

This write-up is partly inspired by a recent post by Vlad Mihalcea on LinkedIn about the recently introduced text blocks in Java. More about them can be read here.

Now, that’s a nice feature. I’ve used it in Scala several years ago, and other languages also have it, so it seems like a no-brainer to introduce it in Java.

But, syntactic sugar (please don’t argue whether that’s precisely syntactic sugar or not) can be problematic and lead to “syntactic diabetes”. It has two possible issues.

The less important one is consistency – if you can do one thing in multiple, equally valid ways, that introduces inconsistency in the code and pointless arguments of “the right way to do things”. In this context – for 2-line strings do you use a text block or not? Should you do multi-line formatting for simple strings or not? Should you configure checkstyle rules to reject one or the other option and in what circumstances?

The second, and bigger problem, is code readability. I know it sounds counter-intuitive, but bear with me. The example that Vlad gave illustrates that – do you want to have a 20-line SQL query in your Java code? I’d say no – you’d better extract that to a separate file, and using some form of templating, populate the right values. This is certainly readable when you browse the code:

String query = QueryUtils.loadQuery("age-query.sql", timestamp, diff, other);
// or
String query = QueryUtils.loadQuery("age-query.sql", 
       Arrays.asList("param1Name", "param2Name"), 
      Arrays.asList(param1Value, param2Value);

Queries can be placed in /src/main/resources and loaded as templates (and cached) by the QueryUtils. And because of the previous lack of text blocks, you would not prefer ugly string concatenated queries inside your code.

But now, with this feature, you are tempted to do that, because, well, it looks good. Same goes for Elasticsearch queries, for JSON templates and whatnot. With this “sugar” you have more incentive to just put them in the code, where they, arguably, make the code less readable. If you really have to debug the query, as opposed to assuming its semantics by its name and relying on a proper implementation, you can easily go to age-query.sql and work with it. Just like when you extract a private method with some implementation details so that it makes the calling method more readable and easy to follow.

Both problems have manifested themselves in my Scala experience, which I’ve summed up in my talk “Scala – the good, the bad and the very ugly” (only slides available). Scala allows you to express things nicely and in multiple ways, which leads to inconsistencies and horrible code readability in some cases.

Counter intuitively, sometimes the syntactic improvements may be worse for code readability. Because they introduce complexity and because they make it easier to do the wrong thing.

That’s not a universal complaint, and certainly syntactic sugar is needed – you don’t have to write List<String> list = new ArrayList<String>() if you can use the diamond operator. But each such feature should be well thought not just for how easy it makes to write the code, but also how easy it is to read it, and more importantly - what type of code it incentivizes.

1 thought on “Syntactic Sugar Is Not Always Good”

  1. > if you can do one thing in multiple, equally valid ways, that introduces inconsistency in the code and pointless arguments

    Yes, absolutely. But, you can’t avoid this with language design.

    Let’s call some task that needs to be programmed ‘task T’, and there, in broad strokes, two ways to do task T within language L: Strategy A, and Strategy B. For example, ‘with text blocks’ or ‘by reading a txt file out of src/main/resources’, but it can be anything. The optimal language design then strongly depends on the relationship between A and B:

    * A is just ‘better’ than B: For all variants of T, A is at least equally succint and/or performant and/or readable and/or easy to transform in the face of future feature requests and bugfixes, and for some or all variants of T, significantly better in one or more of those aspects, __the best language design would make B impossible__ just to help out the coder and prevent them from writing it using the B strategy.
    * The venn diagram is not that simple: A is better than B for certain variants of T, but B is better than A for others. __The best language design would make both A and B relatively easy, and trusts the programmer to pick the right strategy__.
    * The venn diagram is very simple: A and B are ‘equally good’: For virtually all variants of T, it doesn’t really matter which strategy you use; the code looks totally different between the two, but either strategy is about equally performant, succint, readable, and flexible. __The best language design would pick a side and strongly prefer A or B__ and this perhaps surprising conclusion is part of the point of this article, I think, right? (because the alternative: The language should make both easy and thus cater to both programmers whose aesthetic senses have a preference for A, and those who prefer B – also sounds enticing, and e.g. is definitely what scala is trying to do). After all, that almost sounds like the textbook definition of a pointless style debate.

    The problem is, this is all just considering one T. There may be some completely different task U where there is strategy A and strategy C, and A is the ‘superior’ one, and a task V with strategies B and D, where D is ‘superior’. Thus the language should make both A and B easy. Even if, for task T, A is superior, and B should be actively disincentivized.

    The solution to _all_ of these problems is __culture__: Java will allow you to mix tabs and spaces for your indents, but 98%+ of all java coders won’t do that, and will aggressively call it out and fix such behaviour. This ‘culture’ can be codified if you must, in the form of linting tool rules.

    Culture can steer programmers to pick Strategy A when they are programming a solution to a task T even if the language makes B just as simple, and even if for their variant of T, A and B are about equally succint, performant, readable, and flexible. Culture is the solution.

    And this opens up a new dilemma: Imagine a fairly long, 2-line, quote-ridden SQL query. I think for that specific task T, the obvious correct choice given 3 different strategies:

    * Put it in a txt file in src/main/resources
    * Write it in a simple string
    * Write a text block

    Is probably the third one. Had it grown much longer, maybe the first option becomes enticing. However, surely the second option is just bad. It’s the “B” from my earlier story: Possible, but just worse, and to avoid style debates and ease the learning curve of java in general – don’t do that.

    But, java didn’t have text blocks. It is being introduced. Given that java did not have them until very recently, the culture stands no chance: 90%+ of java dev isn’t even done on a version of the JDK that has text blocks. The culture (as defined by tutorials you find, Stack Overflow answers, blogs like this, the /r/programming reddit, example usages on framework pages, your coding team informing you, and codestyle of code you read others wrote) doesn’t use text blocks because they weren’t available yet.

    So, language design can’t help you. But the culture also can’t help you. That seems to suggest the right strategy for the language designers of java is to never introduce new features but surely that’s not the right conclusion.

Leave a Reply

Your email address will not be published.