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?
Reusable Building Blocks
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
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.
Design Patterns
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.
The Tower of Java
Java was designed to be an object-oriented language,
but meanwhile it supports a lot of paradigms and also some patterns:
-
functional extensions provide lambdas,
-
streams provide parallelity and
dataflows,
-
annotations can be used to carry aspects,
-
generics allow classes and methods to abstract the types they work with,
-
access modifiers and the
new modules
provide "adaptive" complexity reduction,
-
Proxy allows to implement interfaces generically,
-
the Memento pattern is coming as Java 14
record (immutable data-class),
-
and we should do literate programming by extensively
writing JavaDoc.
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:
- Java, Kotlin, Scala (JVM languages)
- JavaScript (not yet ES6)
- Python
- Ruby
- R
- WebAssembly
(machine code that can run in modern web-browsers instead of JS,
compiled from C, C++, Rust, Go, ...)
- LLVM Bitcode
(machine code, compiled from many programming languages,
can be converted to many processor instruction-sets and even WebAssembly)
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.
Compilation versus Deployment
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.
Library Dependencies
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 library X version 1
contains class MyX
,
but in X version 2
it was renamed to OurX
.
You use external libraries A
and B
,
A
uses X version 1
, but B
uses X version 2
.
You end up having two different X
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:
- Import statements in classes
- META-INF/MANIFEST.MF
- Spring XML application-contexts (although deprecated it is still everywhere)
- OSGI plugin bundle descriptions
- Maven project-object-models
- JPMS module descriptors
I can't say whether this is the head or the basement of the tower,
but it is crumbling anyway.
Platform Is Not Platform
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.
Conclusion
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:
- readability (more words, less symbols)
- separation of concerns (decomposition facilities like inheritance and aspects)
- error proofing (more immutability)
- complexity reduction (encapsulation, access control)
- portability into other languages (simplicity)
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?