Java and C# suffer from the same ailment

Posted by Berin Loritsch Tue, 29 Jun 2010 16:46:00 GMT

I have an interest in language design, even though I have no direct outlet for it at the moment. So as I’ve been contemplating what I like and what I don’t like about the languages I have been exposed to, I’ve realized that both Java and C# are suffering from the same core ailment. That ailment is the conceptual complexity underlying these platforms. I have to say platform because both Java and C# use a virtual machine that has been used to host other languages as well. C# without the CLR is like Java without the JVM: useless. This is in stark contrast to the almost sublime conceptual simplicity of Lisp, Smalltalk, and even Ruby.

Both C# and Java have bolted on several different features to deal with the underlying complexities, much like the English language has imported words from several different languages. English, technically a Germanic language borrows significantly from Romantic languages like Latin, and even some Greek. We won’t mention some import words from vastly different languages like Japanese (kimono, karaoke, katana, kanji). So it is with Java and C#. A short list of concepts shared by both languages include:

  • Autoboxing
  • Attributes/Annotations
  • Dynamic binding (.Net 4.0 has a DLR and Java 7 has new JVM opcodes for this purpose)
  • For each style iterating
  • API document generation
  • and more…

The problem isn’t so much the features in and of themselves. The problem is more subtle than that. In order to deal with the complexity of the language itself, these features are necessary. In some ways, a language like Lisp has conceptual appeal, even though its syntax is hard to wrap your head around. If everything is a list, from parameters passed in to a function to data values, and the language is built around set theory, it maps pretty well to a discipline of math. Heck, with Lisp a function is just a list of operations. Although perhaps in some ways Lisp is too conceptually simple.

The problem I’m getting at is being able to form a reasonable hypothesis of how the software is addressing your problems. I remember reading a PR piece on how Java was better than C# that had a small snippet of code asking how many method invocations there were. The two or three line snippet actually ended up invoking an unexpectedly large number of methods, from attribute accessors to delegates and some other magic. The intent of the developer was clear, although the impact of the code was unexpectedly complex. That’s not to say that C# is bad. The article was a PR piece to help Java developers still feel good about themselves. However, Java is just as guilty. Have you ever tried to debug dynamic proxy code? Have you worked with features that injected functionality into your code for you (Spring/Hibernate comes to mind)?

Other than the general second law of thermodynamics, what is it that drives languages to be more complex? Rather than truly seeking simplicity, both Java and C# have progressively moved toward sweeping the inherent complexity under the rug. Essentially moving the problem from something the developer has to worry about to something the platform has to worry about. To paraphrase my wife’s favorite movie:

There are three kinds of pipe. You have nickel, and you can see where that’s gotten you. You have bronze, which is very good… until something goes wrong. Something always goes wrong. And then you have copper, which is the only kind I use. from Moonstruck

Programming is a complex process. Translating the sometimes conflicting desires of a human into something a computer can understand is not easy. That complexity is further compounded by the moving parts we need to work together to accomplish our goals. My goal in exploring the world of language design is to find the right path for true simplicity. While we are approaching on that ideal from different programming paradigms, we haven’t quite reached the ideal yet. It feels like we live in a world where there is only nickel and bronze, and copper has yet to be discovered. I’m not the only one thinking about this for sure.

Controller Mixins? 2

Posted by Berin Loritsch Wed, 12 Sep 2007 12:18:00 GMT

One of the things I’m wanting to solve (in a simple and elegant manner) is the problem of caching. Essentially I want to introduce an interface that you can place on things that would be put in a request or session attribute so that I can determine the cacheability of the page dynamically. It would be easy enough to introduce a base class that takes care of this for me. The problem is that not every controller needs to have caching, and some controllers need to force no caching within the same application. Not to mention, if we want to add a set of actions to an existing controller like the Ruby on Rails scaffolding mixin we would be required to either have a multiplicity of base classes or we would need a mixin functionality like Ruby.

Now, the cool thing with ActionPack is that we are already calling methods introspectively on a class, so what could be difficult about calling methods on another controller that is “mixed in” to your controller? That allows you to manage functionality chunks effectively. The mixin controllers don’t have to be (and really shouldn’t be) in the same package as the rest of your controllers. The only issue comes up where a controller has the same action of a mixed-in controller. The action has to be simple and predictable every time. My current thinking is to provide the same action as inheritance. I.e. the new action would override the old action. The only issue comes with calling super.actionName() within the action. How do we capture that? The only way I can think of is that we provide one method to handle the delegation if it is called. For example yield() would tell the controller to look in it’s mixins for the corresponding action and call it.

The idea is pretty cool, and the ability to snap in aspects of functionality into your web application is truly useful. It’s a truer version of separation of concerns without the intrusiveness and lack of clarity as the more traditional aspect oriented programming (AOP) efforts. However, it does introduce a certain intellectual complexity. We have to make it really obvious to developers that mixins are being used, and we have to use a design principle that Java developers are used to. That’s why I intend to imitate the functionality of inheritance as best I can. Mixing in multiple controllers also requires predictability. The order that a developer mixes in controllers must be respected. If two controllers don’t play nice together, the developer needs to have the ability to override the order that the actions are called.

I’m balancing the payoff for the complexity involved. I’m also thinking about how to implement the thing, to keep things as transparent as possible to users of the ActionPack framework, yet apparent as possible to developers of the ActionPack framework. The one thing I haven’t touched is what to do with views associated with the mixins. That’s going to be a big issue to work out later. First I want to do logic only mixins. Yet another question is when are mixins allowed to be added/created? It might be terribly useful to add mixins based on user parameters. For example, if the application supports users from several companies, and they want navigation cues specific to their company then adding in the mixin specific to that company would be something done in a filter. If we provide no limitations, and only require that the mixin is applied before it is used, that might also be an option.

I’m thinking that the mixin approach is definitely going to be used, the question is really how to make it easy to understand .

Annotations: The Language Shoehorn?

Posted by Berin Loritsch Fri, 07 Sep 2007 13:07:00 GMT

A while back, before they had such a wide variety of shoe sizes to fit everybody’s foot we had this tool called a shoehorn. The shoehorn was there to help force your foot into a shoe that no longer fit. Well, it’s purpose was to keep the heel of the shoe from collapsing—but back in the day we used it to get a little more life out of a shoe that was too small. Buying a new pair of shoes was out of the question, because it was a choice between spending the $30 on shoes or some other need that we had. Times have changed, and so has my budget.

Background

Software languages have undergone many different fundamental shifts in thought over the years as well. Originally we had machine language and its symbolic cousin Assembly Language. Originally they had little structure and GOTOs (AKA JMP instructions) were written to specific addresses. Some natural evolutions happened as processors became more complex and subroutines were born, enabling some of the more Calculus like breakdown of applications. The first thought shift was Structured Programming which replaced hard addresses with symbols and registers with variables, and enabled an arbitrary number of variables—despite the limitations of the processor underneath. Then came Procedural Programming with C and its collection of similar languages. You’d think that Object Oriented Programming came next, but that was just one of several other shifts in thought. You also had declarative languages, logic languages, etc. For a longer breakdown you can visit Wikipedia’s Programming paradigm page. Each programming train of thought lends itself to a particular set of problems.

The ultimate sophistication is simplicity. Leonardo da Vinci

There are many aspects to simplicity, and it’s easy for me to throw this term around and add the disclaimer about how it’s easier said than done. Instead, let’s explore what makes software simple. What goes into this word simplicity that I keep harping on?

  1. Economy of concepts. There should be no more than one or two fundamental design principles that are used consistently, and without exception. If there are two principles, the choice between them also has to be consistently applied and clear when to use which principle.
  2. Consistency. As soon as you add one exception to any rule, you’ve just made it a lot more difficult to understand. For you math happy folks think of the complexity in terms of Re+1 where R is the number of rules and e is the number of exceptions (in total). Smaller numbers are more simple and easier to understand. For example, if you have 9 rules and rule 1 has 2 exceptions and rule 3 has 4 exceptions the complexity is 96+1 or 4,782,969. That’s bad.
  3. Predictability. Every action that you can make has to have a clear and predictable outcome. To understand the impact of unpredictability, treat the math for predictability like the math for consistency. For everything that can go wrong, you’ve just made the system that much harder to comprehend.
  4. Self evident. Based on the consistently applied principles and the predictability of actions, the course of action for the user to take should be self evident. In short, once you’ve learned how things work you shouldn’t need anyone to hold your hand when you have to make a change.

Now you get an idea of what I mean by simplicity, and how it is actually hard to pull off for anything but simplistic systems. I mean if you only have two rules and two exceptions you still have a relatively simple system. Of course, once you start adding more rules and actions the complexity increases exponentially.

Annotations Are Mixing Concepts

I’ll be the first to say that Annotations are not completely useless, and they can help address some otherwise potentially sticky problems. However, as soon as you use Annotations to perform a programming task you are using declarative programming. Java and C# are supposed to be Object Oriented Programming Languages. Build languages like ANT and Make are declarative languages. You declare some tasks and their dependencies and allow the build process to sort it all out. Java wasn’t designed like that, but because there were some aspects of programming that are too hard to do in Java’s version of OOP we introduce annotations.

Annotations provide a way to mix concepts in a declarative fashion.

All the Annotation does is mark something in the class with some sort of tag. In essence it’s like a marker interface for the class, method, attribute, or parameter. The most innocuous use of Annotations is simply to mark a method for documentation purposes. It doesn’t take up compiled class file space or runtime memory, just gets added to the documentation. That’s a reasonable and predictable use for annotations.

The tricky part is when we change what the application does when we use annotations. For example, marking methods as a web service endpoint changes how the method is used. Marking a method as a test method affects how that method is called. Or in the case of Java ActionPack, marking a method with @Filter has more side effects. That method will now never be used for an Action, and the filter will be called before/after all the actions that it applies to. I recently added a convenience method so that you can query whether an action is marked with an annotation or not.

I used this solution so that I could mark all my AJAX support methods with the @AJAXSupport annotation. Why would I want to do that? Two reasons: for documentation purposes, and so that I don’t have the Single Sign On (SSO) service bring me back to an AJAX support page. The system would automatically set a callback URL in a filter applied for every action in the system. If someone called an AJAX support action that returns a partial page result, and then clicks on the SSO link the return URL from the SSO service would be for the partial page result. I had to do something (other than manually listing the actions and testing each one of them) to prevent that from happening. So I added an annotation so that I could check if the action has that annotation or not.

When I explained that to someone on the team, they responded with “I would never have guessed that”. Of course they wanted the equivalent of a modal dialog box on the web—which I believe is bad even in desktop application design. Another complaint was that I was mixing application logic with display logic. To be fair, it is navigation logic which is sometimes considered application logic and sometimes considered display logic in different situations. However, from a pure “separation of concerns” principle I was now mixing concerns in my code.

Does that mean that the solution was bad? It works, and it was the simplest thing to maintain and perform. It does not mean it is the most elegant solution or the purely simple solution. For one thing, I’m mixing declarative logic with object oriented logic. Another problem is that I’m mixing navigation logic with application logic. To some extent you can’t get around that, but that can be a symptom of design smell as well.

So am I saying that Annotations are evil, and because they are mixing concerns and programming paradigms we should avoid them at all costs? No. I’m saying that they trade code complexity for conceptual complexity. The complexity is not really gone from the system, it’s just that it has been moved to a different form of complexity. You might be able to grasp each concept in isolation, but mixing the two will probably make it hard to maintain in the future.

Part of the reason that Ruby on Rails works so well is that they did keep the concepts as simple as they could. There are relatively few rules to learn, and the rules are applied consistently. It was also built on a language that was flexible enough to keep everything object oriented, so there is no need to resort to the declarative annotation model.

Justification for On Complexity

I chose the formula because as soon as you introduce one exception, the human mind has to sort out whether or not that exception applies to this rule. Even if there is one exception to a list of 10 rules, we have to recall whether the rule we are using has the exception or not, and if it does whether the exception is in effect. A list of rules with no exception still has the complexity of deciding which rule (or set of rules) applies so it has a base exponent of 1. You add one exception, and you’ve squared your complexity. For example a set of ten rules with no exceptions has a complexity score of 10, and as soon as you add one exception it becomes 100.

The complexity values have no units and no real meaning other than to provide a relative indication of what you are getting yourself into. It helps you understand the number of decisions the brain has to sort through before it is satisfied that it can predict that you are doing the right thing. Sometimes a little change can provide a huge impact on the relative complexity. The idea is to keep the numbers as small as you can.