Blog-Archiv

Sonntag, 25. März 2018

A TypeScript Test Installation

Any JavaScript (JS) developer that is tired of fixing bugs that arose from silly programming mistakes sooner or later will switch to a typed programming language. The new ES6 language doesn't yet provide static type checking, although everything is ready for it. Besides the JS alternatives Dart (Google), Elm, and CoffeeScript, there is also TypeScript (TS). Originally started by Microsoft in 2012, it was also promoted and updated by Google's Angular development team since 2016. TypeScript is a super-set of JS, i.e. every JS code is valid TS, but not any TS code is valid JS. Surprisingly TypeScript is much more similar to Java than JS is.

Target of this Blog is to install TypeScript and write a HTML page that uses it, proving that both dependency management and type checks work.

Installing TypeScript

The TypeScript quick-start tells me that there are two ways to install the TS compiler (tsc):

  1. via nodejs package manager npm
  2. via VisualStudio Code IDE

No way to get it without surrounding tools? I decide for the lightweight nodejs variant.

NodeJs

NodeJs makes JavaScript available as "standalone" programming language. JS originally was made for browsers only, accessing the HTML DOM (document object model) and BOM (browser object model), not files, not input- output-streams, not network sockets (except AJAX). Having such functionality in a browser would open your computer completely to the Internet! The only thing a web-page is allowed to store on the browsing computer are cookies, and, since HTML-5, name/value pairs in a local storage.

On server side it absolutely makes sense to access files and other computer resources. NodesJs was started as server-side JS. You can use it also as web server for testing. Mind that when you run a JS interpreter standalone, without a browser as environment, global variables like document or window will be missing. Thus not any script that can run in nodejs can run in a browser, and not any script that can run in a browser can run in nodejs.

But this Blog is not about nodejs, I need it just to install TypeScript.

I'm on my Ubuntu LINUX machine in a command-line terminal window, and I follow the according nodejs instructions, using the native package manager apt. I have curl ("See URL") already installed (do it via apt-get install curl when not):

  curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
  # Make the NodeSource download-repository available for the operating system.
  # Reads a script from the web and executes it as superuser. Yes, this is dangerous.

  sudo apt-get install -y nodejs
  # Actually installs nodejs from that repository.
  # -y: Assume "yes" as answer to all prompts and run non-interactively.

So let's see what we have after installing:

  nodejs --help
  # Her majesty the JavaScript interpreter itself
  # 'node' is a deprecated alias for nodejs

  npm ls
  # Lists all installed nodejs modules.
  # npm spells "nodejs package manager". 
  # It can download and upload libraries, and manage dependencies.

Remember that JS has no built-in dependency management, no include, no import, dependency was expected to be done by the order of HTML <script> elements. This was one of the biggest flaws, and was fixed just recently by ES6. Mind that plain old JS still doesn't support the ES6 import statement, and old browsers may still not support ES6.

TypeScript via npm

In my terminal window, I enter just a single line to install the TypeScript compiler:

  sudo npm install -g typescript
  # -g means "global", install to machine, not to current working directory.

  tsc --version
  # The TypeScript compiler is installed now and globally available.

TypeScript source files traditionally have the .ts extension. Let's try out if it works:

  echo "console.log('Hello World')" >hello-world.ts
  # Write some test source into a TypeScript file

  tsc hello-world.ts
  # Compiles it to hello-world.js

  nodejs hello-world.js
  # Executes hello-world.js, will output >Hello World<

So nodejs at least supports the global console object. Now let's try out a real web page that imports JS compiled from TS.

Dependency Management Test

TypeScript as language implements the ES6 specification concerning dependency management. Most browsers already support it.

For this test I will stick to ES6 import statements, because in future browsers will do dependency resolution. Let's see if TS can handle it, and still provides type-checks for imported functionality. I need an HTML page, there I will refer to an external JS file which was compiled from a TS file.

TS

A basic module hello.ts is responsible for saying "Hello". Use a plain text editor to paste following into a hello.ts file in current working directory:

hello.ts
1
2
3
export function sayHello(name: string) {
    return "Hello "+name;
}

To test dependency management, the page-module index.ts imports hello.ts:

index.ts
1
2
3
4
5
6
7
8
import { sayHello } from "./hello.js";

function displayHello(elementId: string, displayText: string) {
    const element = document.getElementById(elementId);
    element.innerHTML = sayHello(displayText);
}

displayHello("hello", "TypeScript");

HTML

Now I integrate the TS compile-result index.js into a test page index.html:

index.html
<!DOCTYPE html>
<html>
    <body>
        <p id="hello">Loading ...</p>

        <script type="module" src="index.js"></script>
    </body>
</html>

Compile and Test

In command-line terminal, compile the TS sources:

  tsc -t ES6 *.ts
  # "-t ES6" tells tsc that the type of sources is ES6. 

When you load the index.html page into your browser, you should see following now instead of "Loading ...":

Mind that this will work only with browsers that already support ES6 modules! When you look at index.js, which is the compile result of index.ts, you see that the tsc compiler simply left the import statement as it was.

Import Type Check

To try out whether TypeScript also follows the import and can detect type errors there, I create a type mistake in hello.js.

hello.ts
export function sayHello(name: number) {
    return "Hello "+name;
}

As you see, I changed the type of the name parameter from string to number. I would expect that the tsc compilation fails now.

  tsc -t ES6 *.ts

Positive! Output is:

  index.ts(5,34): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

Conclusion

The shown TypeScript test environment can be built also on WINDOWS and APPLE operating systems. Of course there are lots of tools that automatically compile sources, e.g. tsc -w will watch all given files and re-compile them as soon as they change. The nodejs world is big, and there are tools for everything that eases your life as a TS developer :-)




Sonntag, 18. März 2018

Java NullPointerExceptions

The NullPointerException is the most frequent error in Java applications. It seems to be easy to find through its stack trace, and also easy to fix by a simple if (x != null) condition. But reality quite often plays a different game than the developer. Essentially I would call it dangerous to fix any NullPointerException by a simple if-condition. This could hide conceptual bugs that are not so easy to see. When there is no assert and no comment about it, you really should investigate why the thing is null.

Hard to Find?

Sometimes it is really hard to see, even when you have a stack trace. Look at following example.

Autoboxing is one of the most hated Java features, introduced in 1.5. It may make the life of certain programmers easier, but it imposes a constant threat to all. I fixed many such pitfalls, but still need a long time to recognize this bug. (Personally I decided to not use autoboxing, so I am not used to its failures.)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/**
 */
public class NullPointerFromAutoBoxing
{
    public static void main(String[] args) {
        new NullPointerFromAutoBoxing().run(null);
    }
    
    void run(Boolean autoBoxed)    {
        dispatchParameter(autoBoxed);
    }
    
    private void dispatchParameter(boolean justTrueOrFalse)    {
    }
}

Would you expect that this code throws a NullPointerException?
It does.

Exception in thread "main" java.lang.NullPointerException
 at exceptions.NullPointerFromAutoBoxing.run(NullPointerFromAutoBoxing.java:10)
 at exceptions.NullPointerFromAutoBoxing.main(NullPointerFromAutoBoxing.java:6)

What happens here? The programmer didn't mind about the difference between primitive boolean and class Boolean, because auto-boxing promised that Java will care for that. But it can do that just for Boolean objects that are not null. Thus, in the run() method on line 10, Java tries to convert the parameter autoBoxed (which is null) to a primitive boolean and fails, because primitive boolean doesn't have a representation for null, it can be just true or false.

The programmer did nothing wrong here, it is Java that fails. So consider if you still want to use autoboxing.

Prevent NullPointerException

When you call a method on an object that you would not expect ever to be null, then you should tell this in a comment. Making no statement about it causes long investigations when it actually happens.

final Runnable closure = ....;

closure.run();  // must throw NPE when null

That way you can point to the fact that it is a fatal error when closure is null. Even better would be to explain how and where to fix the problem when happening. Here is the advanced version:

final Runnable closure = ....;
assert closure != null : "The creation of closure failed, check its factory!";

closure.run();

Asserts have to be switched on explicitly via -ea ("enable asserts") when running the Java virtual machine. That may not be the case when the application is deployed. Thus the safest version is following:

final Runnable closure = ....;
if (closure == null)
    throw new IllegalStateException("The creation of closure failed, check its factory!");

closure.run();

Conclusion

The null-problem is quite old, once called the billion-dollar-mistake. I do not consider it to be a bug. It was the right decision to leave this up to the developer. Suppressing a NullPointerException would mean obscuring a programming mistake.

It is interesting how new languages like Kotlin try to get around it. Things like "Elvis Operators", "Safe Call Operators", or "!!", make our code even more unreadable, but marking parameters and variables as not-allowed-to-contain-null absolutely makes sense ("Non-Null-Type"). Also TypeScript provides such an operator. Would be nice to have this in Java too.