In this Blog I will write about the softare development world and where it may be going to. In my 30 years experience I had several programming languages under my fingers. My work evolved from self-written leap year functions in C to reusing Java libraries for anything and everything. Will it stay like this, or are there new programming concepts and languages ahead?
First let's see where we are coming from. Java builds upon decades of language experience and offers following capsules for our precious source code:
Methods receive parameters and can return a result built from these parameters and calls to other methods. Since Java 8, methods can be passed around like functions. Pure functions do not use global variables, and thus have no side-effects in case all called functions are pure too.
public int add(int a, int b) {
return a + b;
}
A function is a reusable atom, you can not extend it in the sense of inheritance.
Classes are templates for dynamically constructed objects that can be used through their methods. Classes are the context for fields (data) and methods (functions) that work on these fields. A class can contain inheritable inner classes down to any depth. It can import other classes it depends on.
package my.arithmetics;
public class Addition
{
private final int a;
public Addition(int a) {
this.a = a;
}
public int calculate(int b) {
return a + b;
}
}
You can extend a class and override methods to customize the class to a new behavior, thus it promotes code reusage.
Modules (available since Java 9) are sets of classes inside package folders, one or more packages being exported, maybe depending on other modules. Modules are compilation units, but modules that offer services are also separate deployment units.
module my.arithmetic
{
exports my.arithmetic;
requires my.logging;
}
Modules are more like atoms. They do not allow packages of same name in other modules ("split package" constraint). Although you can extend a single exported class from a module, you can't extend (customize) a class-graph unless it implemented the factory-method pattern consequently (which is rarely the case), and all wrapped graph classes are overridable and have been exported.
Once applications consisted of fields and methods. The object-oriented idea bound these two together to classes. Now modules are containers of classes. Are there more layers to come? Wouldn't it be possible to have just one construct that covers all these levels, recursively, so that we won't need hypermodules any more?
To get a better feeling for a possible future constructs, let's have a look at programming paradigms.
Programming paradigms (like domain-specific languages) reflect real-world conditions and determine the way how we solve problems. Here are some paradigms that impressed me:
Object-oriented:
Classes are customizable compilation units, access modifiers reduce complexity, applications are graphs of dynamically built objects; OO languages deliver well maintainable and big applications when being strongly typed.
Functional, Function-level:
No variables, just constants, functions that, other than procedures, return values, functions can be passed around like objects; functional languages have proved to deliver the most failsafe implementations.
Generic:
Algorithms do not specify the types on which they operate, so that they can be reused for various types of data; the archetype of a framework.
Aspect-oriented:
Global pieces of logic (crosscutting concerns, advices) to be done at different places (join-points, defined by pointcut) of an already existing application, like access-control, logging, transaction management; source code would get redundant and cluttered if join-points implemented these aspects by themselves.
Adaptive:
Following the "Law of Demeter", implementation units don't talk to strangers, just to their immediate friends; a way of complexity reduction through strong encapsulation, has now merged into aspect-oriented programming.
Automata-based:
Every computer application is a state-determined automaton, thus it should be implemented in terms of states, events, transitions and actions.
Parallel programming:
Modern computers with several processors can be used efficiently only when the software enables that.
Literate:
Donald Knuth's convincing idea of having source code embedded into documentation, not the other way round.
One way to solve programming problems was called "Design Patterns", published in the Nineties for C++ ("Gang of Four"). They still play a role because they do not depend on a specific language, although often being object-oriented. Somehow they relate to programming paradigms, especially to generic programming. It's not possible to provide superclasses or frameworks for design patterns.
Design patterns didn't get very popular because they require advanced programming skills, and developers love to create their own solutions and patterns. Moreover they are so close to each other that it is hard to understand the differences between them, see Builder and Abstract Factory. Nevertheless studying design pattterns broadens the horizon.
Now let's have a look at the current Tower of Babel.
Java was designed to be an object-oriented language, but meanwhile it supports a lot of paradigms and also some patterns:
Last not least, the biggest feature, Java apps can be run on any platform that provides a Java virtual machine.
So, what's the point, why do I bother about the future of programming and Java?
Java was a real simple language back in 1998 when the JRE had 20 MB. Although lacking most of above features, Java source-code was well readable and robust, much better than C and C++, and thus maintenance-friendly. Nowadays, due to the many features added, Java has become quite a complex language, and paradigms flowing in made it an expert realm. Let me give examples.
There are Java libraries that use reflective techniques where you can write code that seems to make no sense for the average programmer eye, like Mockito unit test code:
Iterator<String> iterator = Mockito.mock(Iterator.class);
Mockito.when(iterator.next()).thenReturn("Mockito");
This code, technically, can not be understood just with knowledge about object-oriented languages. You need knowledge about the Mockito library and Java proxying techniques, in other words, you possibly will have to read complex documentations to be able to maintain such code. Of course you can say "just read the words and believe them" (intuitive programming?), but developers are not paid to be believers, and they do not read code, they scan it with the language syntax in mind.
What we see here is called "stubbing". It has nothing to do with DLL stubs. In context of the mockist style of test-driven development, "stubbing" means "teaching behavior". The static Mockito.when()
method teaches the Mockito-generated Iterator
to return "Mockito" on first call of next()
.
Spring, also a Java library, and especially Spring Boot, are on another level. The latter not just uses inversion of control (IOC), it completely takes over the program flow and puts it into annotations. In other words, you can not understand a Spring Boot application without knowing the semantic of Spring annotations. Spring Boot is almost irresistable, because it leads Java towards cloud computing.
What was that about "open source"? We are allowed to read it. But can we also understand it?
@Configuration
public class OutputStdoutConfiguration
{
@Bean
@ConditionalOnProperty(name = "output", havingValue = "stdout", matchIfMissing = true)
public PrintStream outputStream() {
return System.out;
}
}
@Configuration
public class OutputStderrConfiguration
{
@Bean
@ConditionalOnProperty(name = "output", havingValue = "stderr")
public PrintStream outputStream() {
return System.err;
}
}
@Service
public class OutputServiceImpl implements OutputService
{
@Inject
@Qualifier("outputStream")
private PrintStream stream;
@Override
public void println(String line) {
stream.println(line);
}
}
What this Spring source is for: You can configure either stderr
or stdout
as output stream through an application-[profileName].properties file containing either output=stdout or output=stderr, or neither. Logic packed into annotations and conventions, spiced with magic string programming. 50% of the lines are annotations.
Spring started as IOC container, which is a concept opposite to the object-oriented idea, but useful for customer-specific configuration. Today Spring is a big developer movement providing functionality for nearly everything, focusing on Metaprogramming, whereby IOC's dependency injection (DI) is just the base technique for integrating that functionality. I call Spring "Java for superheroes":-)
How DI works: The Java compiler checks the correct usage of Java interfaces but doesn't require an implementation being behind it. When a class uses another class through its interface, DI can fulfill that interface by one of its implementations at runtime (loose coupling) via reflection. This is used for runtime-determined configuration. The application will be compile-clean due to the interfaces, but it won't function unless deployment puts the configured interface implementation somehow onto the CLASSPATH.
Unfortunately Spring is not a deployment tool. As script languages are runtime-determined-only, I could say that Spring turns Java into a script language. Compilation always succeeds, startup often fails. SmallTalk, one of the greatest models for Java, died of that disease.
Global variables: one of the oldest problems of the programming world, causing hard-to-find and annoying errors. Programmers love it, because they don't need to define a parameter when it is global. Object-oriented languages reduce globals to class scope. Nevertheless, in Java, globals can be defined through public static fields/methods inside classes.
A Spring application context is a container for singleton objects, and an instance factory for "protoype" objects. Although Spring contexts are not singletons by themselves, I have never seen an application that uses several contexts. The Spring context is always used as a static singleton "programmer's heaven", with all the consequences of globality coming back.
But we can't blame Spring. It's not the tool, it's us that are the fools. We would need fool-proof tools.
Oracle promotes a "polyglot" language environment called GraalVM, replacing the Java VM, supporting following languages:
Let's see what overwhelming freedom causes. Most likely every developer will want to distinguish himself through his own programming language. What is a dream for developers can be a nightmare for software maintenance. Be sure that this won't reduce the complexity of your project.
All these things make me call information-technology the Tower of Babel. The Java Tower is already crumbling, because a tower with too much freedom will come down one day, that's what Murphy taught us.
The compiler is the tool to tackle complexity. It can check thousands of functions, classes and modules whether they fit to each other. But it won't go beyond interface boundaries, because it is impossible to check what only deployment will decide. In times of componented-based software development we need this runtime-determined borderline.
Speaking in JPMS modules, this line will be drawn through services, speaking in Spring, the line is drawn wherever Spring beans are used. Once again we have this ambiguity that makes up the cracks in the Java Tower.
It's not the language, it's the libraries that make us great. We do not implement quicksort and search-replace by hand any more. The many open-source libraries are one of the reasons why the Java Tower grew so high.
Consequence is a big hierarchy of library dependencies that tend to get out of control due to their maintenance versions. It is the proverbial DLL hell, LINUX has its own variant of the same problem:
External libraryX version 1
contains classMyX
, but inX version 2
it was renamed toOurX
.
You use external librariesA
andB
,A
usesX version 1
, butB
usesX version 2
.
You end up having two differentX
versions on your CLASSPATH, and which one gets loaded at runtime is not predictable.
The deployment tool Maven does its best to keep Java dependencies under control, but some problems can not be solved. This is the reason why Java introduced modules. The Java 9 runtime library (JRE) has been refactored to be modules, Java libraries around the world are following slowly in case supporters are still present. Applications around the world wait for tools that make their modular life simpler. Modules are a wonderful stabilizing support for the Java Tower, but they come a little late.
In future, Java dependencies will be defined in following places, most likely redundantly and contradictory:
I can't say whether this is the head or the basement of the tower, but it is crumbling anyway.
Ten years ago the word "platform" designated operating systems, i.e. WINDOWS, MAC and several UNIX systems like LINUX. Nowadays smartphones took their place in our lives. None of them supports Java applications. Although Android allows programming in the Java language, its class file format and virtual machine is different from original Java. You may be able to compile your Java business logic to Android, but you will have to rewrite any user-interface based on AWT, Swing or SWT.
This is the reason why many web pages complain about the broken Java promise to be platform-independent: the meaning of "platform" has changed since that promise.
Providing several programming paradigms in a language leads to different ways how developers solve problems. Staying purely object-oriented leads to introduction of domain-specific languages that can solve problems more elegant. Mastering several programming languages at the same time requires advanced skills because of the different language grammars, therefore I would prefer just one language representing several paradigms. But this question always seems to be about a few lines of code less. Of course shortness is a criterion, less lines of code are less bugs, but a little boilerplate doesn't hurt anybody and may increase readability.
I don't believe in something like "intuitive programming". A programming language must provide a precise and restrictive syntax to avoid all those human mistakes that happen during development, along with an unambiguous common sense how problem soutions should look like. There should be just one way to do it, freedom is inappropriate when it comes to languages. What counts is:
Although the Java Tower is crumbling I don't see anything better at the moment. Kotlin provides a little less lines of code and a trendy syntax. Only the aspect-oriented idea bears innovative power. At the time being it is a language that sits on top of another language. Would it be possible to compose an application from aspects only?
ɔ⃝ Fritz Ritzberger, 2021-01-19