Two Ways in Which Java Didn’t Get It

While Java is a very practical language, used nowadays to implement a large array of software applications, it also has its own share of problems. I am not the first and won’t be the last person to touch on the many issues raised by its design, especially in the areas of syntax and support for non-OO concepts.

While design problems are an interesting issue for technical discussion (especially if you are a programming aficionado), it is clearly not the main reason why someone would decide to use a language or not. Mostly, you have to answer pragmatic considerations such as “do you know how to use the language well?” and “does the language provide support for the type of problems I want to solve?”

Despite this, here I will consider only two of the design problems in Java that I haven’t seen discussed in other places.

Safety versus Performance

The first problem is related to the security focus of the language. As Java was initially sold as the language of the Internet, the fact that it was designed for safety looks like a very important issue. After all, nobody wants to download and install software that is unsafe and untrusted.

The mistake of Java, however, is that it tries to achieve safety at the level of byte-code execution, thinking that this is the most important aspect of security – or at least so important that it would be able to fix most of the security issues we might encounter in a networked world. For example, every time you load a Java class (or set of classes), the runtime mechanism has to spend a good amount of time trying to determine if the code is safe or not.

Security checks at the byte-code level take time and effort from the part of the runtime system. The big problem with this, however, is that only a small fraction of all code written in Java really needs the security checks that are in place. For example, even if you’re writing code that will never be in contact with the Internet, you still need to pay the price for this heightened level of security when using the Java runtime. The unintended consequence of such decisions is that the security system exists mostly to slow down the execution of programs, while complicating the class loading process.

Moreover, it is now understood that security at the programming language level is of very little importance for the safety of the overall system. If a malicious programmer wants to do something bad through a downloaded application, there are thousands of avenues that can be pursued, for example, by just getting enough information directly from the user. In most cases this can be done without even having to bother executing unsafe code.

That is why modern mobile applications are digitally signed: to make sure you know its origin. Trust, in the digital world as everywhere else, comes from knowing who created the code. It is part of a reputation system, not simply result of the analysis of the code itself. Moreover, with all the security measures imagined by its designers, Java turned out to become a big vector of malware infection. So much for the supposed safety offered by the language.

Over-Engineering the JVM

Another important innovation of Java is that it made the JVM, its virtual machine, the center of attentions. Unlike other languages that consider the description of the virtual machine simply as a detail that is hidden from users, Java decided to make it a public requirement for implementers, in order to guarantee the compatibility of code written at the JVM level and provide the Java-compatible seal.

While there are interesting advantages in using a well-defined set of operations interpreted by a virtual machine (for example, the ability to transfer compiled code over different architectures), the end result of this type of low-level focus is to bring unnecessary attention to specific areas, in detriment to the overall needs of the language. Java is nowadays certainly paying the price for these early decisions.

In a new language, the syntax and runtime system should be the main focus – not the virtual machine where the programs are executed. Case in point: the implementation of Javascript is constantly improving, with a performance that nowadays approaches (and sometimes exceeds) that of Java, at least for some application areas. The main reason is that designers of Javascript engines have fewer limitations on what they can do. In particular, they can improve the intermediate code as the execution engine matures and new approaches for optimization are necessary.

The same has been true for other interpreted languages, such as Smalltalk. But this natural evolution of the op-codes and other internal representations of compiled code cannot be easily achieved in Java, because the definition of the JVM over-specifies its execution mechanism. For example, it determines the exact representation of the compiled byte-codes. Therefore, even if a better way of representing and executing Java programs becomes available, it cannot be used by compliant virtual machines.

These are issues that may be seen as easy to overcome, but that in fact permeate the way the language is approached by users and implementers. Avoiding this is not possible for Java anymore, and programmers have to learn to live with the limitations of these early decisions, or look for better ways to express themselves. Either way, it is important to learn from past problems and avoid repeating them. Newer languages such as Javascript seem to have used a few of these lessons.

About the Author

Carlos Oliveira is a software engineer and the author of Objective-C Programmers Reference.

Related Books

Here are some other books on this topic:

The Pragmatic Programmer: From Journeyman to Master

  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • E-mail this story to a friend!
  • HackerNews
  • Reddit
  • StumbleUpon
  • Twitter

Categories and Private Methods in Objective-C

In an Objective-C class it is possible to declare instance variables with different levels of visibility. For public variables, any class or function that has a pointer to the object can access the content. Private ivars, however, are available only to the class itself.

Objective-C also provides protected instance variables, which can be read or written by the class where they are defined. These variables can also be accessed by any class that inherits from the containing class. These accessibility levels provide some assurance that the compiler will enforce the visibility of a particular variable. This, in turns, contributes to a better code organization.

It would be great to have the same levels of accessibility for methods, as well as for ivars. However, Objective-C does not work in this way. Messages are resolved only at run time, which means that the compiler cannot enforce such access rules during compilation.

While there is no way to enforce access rules on methods, we can use categories as a simple technique to reduce methods visibility. This is possible because a category can be created as an effective way to encompass one or more methods that are known only in the local (file) context.

Defining Categories

We need to define a new category at the beginning of an implementation file. You can use any name for that category. Traditionally, people have used the name Private for this purpose, but Objective-C also allows the name of the category to be empty, resulting in anonymous categories.

In that case, we don’t need to worry about the right name for the category. But the greatest advantage of using anonymous categories is that there is no need to create a separate implementation section in your file, thus reducing the amount code to maintain.

The content of the anonymous category will be composed of all methods we would like to make private. These methods can be accessed only by code pertaining to the original class and contained in the same implementation file. Here is an example, where we implement a Library class with an anonymous category. The only method in that category is getPrivateBookTitles.

// file: Library.h
@interface Library : NSObject
{
	// instance variables here
}
// standard methods here
@end

// file: Library.m
@interface Library ()

- (NSArray *) getPrivateBookTitles;

@end

@implementation Library

@synthesize numericProperty = _numberOfBooks; // give a different name

- (NSArray *) getPrivateBookTitles
{
	NSArray *books = @[];
	// code to find private books 
	return books;
}

@end

Even when using anonymous categories, it is necessary to keep in mind that the mechanism doesn’t offer run time protection for methods declared in this way. After all, it is still possible for the user of a class to send a message that will be resolved to a method in such a category.

Remember that the mechanism of message dispatching works in such a way that doesn’t care about the target of the message – as long as the message can be dispatched successfully. If you really don’t want some code to be executed by external classes, the best way to achieve this would be to use a static C function.

About the Author

Carlos Oliveira is a software engineer and the author of Objective-C Programmers Reference.

Related Books

Here are some other books on this topic:

Programming in Objective-C

Objective-C Programming

  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • E-mail this story to a friend!
  • HackerNews
  • Reddit
  • StumbleUpon
  • Twitter

How to Handle Debugging Problems

Debugging is hard, and one of the main reasons why programmers spend so much time fixing previous issues. It is no surprise that spending time on code debugging will inevitably reduce the available opportunities for more productive endeavors.

That is why it is so important to learn how to handle bugs efficiently. First, let’s see the main culprits for the introduction of bugs in software. Here is my list of the top items that contribute to their occurrence:

Unclear code: if your code is hard to read, it will also be hard to maintain. Problems usually happen in areas that are poorly understood. You should strive to make your code as clear as possible, and reduce the possibility of errors caused by hard-to-read software.

Badly engineered code: another big culprit is code that hasn’t been designed correctly. If your design doesn’t work from the beginning, there is not way to avoid bugs.

Bad requirements: sometimes the problem is not on the programmer, but on requirements that change, provoking problems down the road when the system is integrated and used.

To handle these problems, you need to do some reverse engineering, and think about the problems I mentioned above.

Is the code unclear? If so, how would you write it in a clear way? Even if you cannot rewrite the system to improve it, you will at least have a mental model of how it should work. And in the process of fixing the bug, you can most certainly clarify the way the code is used, so that future problems will occur less frequently.

Is the system badly engineered? When this happens, you can propose ways to fix the system. Redesigning how the whole system works can avoid big trouble in the future. So, the occurrence of a bug may very well be the sign that the system needs additional work on the design side.

Bad requirements are the problem? In this case, you should clarify the requirements, and probably write them down so that in the future you can refer to them. Business requirements are a common source of confusion, because different users have a different view of how the system should work. As a developer, your work should be to come up with a clear solution that can satisfy the basic needs of all users, if possible.

  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • E-mail this story to a friend!
  • HackerNews
  • Reddit
  • StumbleUpon
  • Twitter