
Add detailed message to NullPointerException describing what is null - dhotson
https://openjdk.java.net/jeps/8220715
======
cpeterso
When Firefox tried to make the "X is undefined" JavaScript exception message
user-friendlier, it broke flipkart.com. The website's JavaScript explicitly
depended on the exact wording of exception messages. Simply loading the
flipkart.com home page caused "X is undefined" exceptions, which it tried to
parse with regular expressions. The new exception message had to be reverted.
:(

[https://www.fxsitecompat.com/en-CA/docs/2018/improved-
javasc...](https://www.fxsitecompat.com/en-CA/docs/2018/improved-javascript-
error-message-breaks-code-relying-on-the-legacy-format/)

This is an unfortunate example of Hyrum's Law: "With a sufficient number of
users of an API, all observable behaviors of your system will be depended on
by somebody."

[http://www.hyrumslaw.com/](http://www.hyrumslaw.com/)

~~~
rwz
Software relying on undefined behavior like that deserves to be broken and
made fun of. I fully support breaking it and the idea that they had to revert
the error message change horrifies me.

This is why we can't have nice things.

~~~
MrStonedOne
I wouldn't be so quick to jump to that conclusion. Was there a better, more
defined way to catch that error type globally? if there is not, that is not
the site creators fault, that is on mozilla

~~~
TheRealPomax
Not sure I follow, if your code throws an exception, you should be able to
code against why that exception would have been able to occur, not against
"the toString() output of the exception", especially in a landscape with
multiple browsers that all have different exception text.

------
runarberg
In JavaScript I often find my self running the code in both firefox _and_
chrome to get the full picture of _what is undefined_ : (firefox)

    
    
        window.foo.bar
        //=> TypeError: window.foo is undefined
    

and _what I 'm trying to get_: (chrome)

    
    
        window.foo.bar
        //=> Uncaught TypeError: Cannot read property 'bar' of undefined
    

Personally I find the firefox error message more useful, but often I get a
better understanding of whats wrong when I run it in chrome. I don’t know why
they are mentioning undefined at all and don’t say something like:

    
    
       TypeError: Cannot read property 'bar' of 'window.foo' (undefined)
    

\---

Edit: Formatting

~~~
bzbarsky
The worst part is that Firefox tried to change the error message it throws to
include the "bar" piece of information, and that had to be reverted because it
broke sites that were parsing the exception message with regexps. :( See
[https://bugzilla.mozilla.org/show_bug.cgi?id=1498257](https://bugzilla.mozilla.org/show_bug.cgi?id=1498257)
for the gory details, though it was not the only site affected: see also
[https://bugzilla.mozilla.org/show_bug.cgi?id=1490772](https://bugzilla.mozilla.org/show_bug.cgi?id=1490772)
(fixed by site author) and
[https://bugzilla.mozilla.org/show_bug.cgi?id=1512401](https://bugzilla.mozilla.org/show_bug.cgi?id=1512401)
(fixed by the backout).

~~~
crdrost
Wow. One would generally not consider exceptions part of a public API but I
suppose they really are; if one rewrites it as a sort of Haskell-ish `Input ->
Either Error value` one can see this directly... then if one church-encodes
one also gets the way JS promises work, `Input -> forall z. (Error -> z, value
-> z) -> z`...

I suppose that the "normal" response is just to say "yeah people will write
bad code in any language," but maybe there was something we missed out that
could have signaled to someone that this was a very bad thing they were doing?
It's something that could easily happen to a junior developer. So if we had
the language of our dreams, is there some other option?

Erlang's "just let it crash, do not attempt recovery but rather monitoring"
comes somewhat to mind. Standardizing the error message also comes to mind,
but seems a bit banal. Error codes are handy but with C I have noticed that
every single piece of software has some different idea of which integers mean
what things, so that developers have to absorb a hodgepodge.

Really it's I guess that you don't want any sort of conditional dispatch to
happen on such a value? You would like the ability in an hypothetical
programming language to mark a field as "advisory", and then the programming
language checks that it never performs any sort of conditional dispatch on an
advisory field. That gives you a way to make the API contract, "this contains
a string but I might change that string at any later date." So if a junior dev
runs into that, they are intimidated and get that sort of instant feedback
they need.

~~~
Boulth
> Wow. One would generally not consider exceptions part of a public API

Interestingly this happened with Java exception stack traces too. Joshua Bloch
remarked that one needs to provide access to all info through standard API, if
it's not there people start using toString and others and it bacames a de
facto API.

~~~
flukus
Even then exceptions are likely being logged and the 'API' may be getting
consumed by other processes, reporting scripts, log formatters, email rules
and god knows what else. Changing an exception string can ruin a lot of
peoples day.

------
Insanity
This sounds like a good addition.

It happens quite often that we get a bug report with "We got a NPE". Our users
often attach a screenshot, just showing the standard "NPE error" message,
which does not really help.

~~~
jschwartzi
That's like every bug report I get from non-technical users at my company.
"The device doesn't work. Can you log in and take a look at the logs?" Yeah,
if you can give me some information about which device it is I'd be happy to.

------
evancox100
> Computation overhead > NullPointerExceptions are thrown frequently.

"Frequently" is obviously a relative term, but are NullPointerExceptions
really common enough to be a performance concern? It's good that they are
taking performance overhead into consideration of course, I'm just surprised
it's even an issue.

~~~
paulmd
A lot of those NullExceptions (and a lot of boilerplate code) could be linted
away if Java gave compiler-level semantics to @NotNull annotations.

Letting null values propagate through your program is frequently a code smell,
you want to constrain it at the boundaries of your program logic if possible.
Unfortunately, Java does not have a semantic way to do this, just
documentation and discipline.

~~~
mrighele
Checker framework [1] can be used to add compile time checks on such
annotations. Unfortunately I found it non trivial to setup (that was some time
ago though, I'll have to check again). I noticed also that a recent version of
Wildfly application server can do those check at runtime, but I haven't
investigated it enough to see if it is configurable and the performance impact
of the checks [1] [https://checkerframework.org](https://checkerframework.org)

------
theandrewbailey
This would be good, but I'd prefer that Java have something like the null
operators that C# has.

[https://docs.microsoft.com/en-us/dotnet/csharp/language-
refe...](https://docs.microsoft.com/en-us/dotnet/csharp/language-
reference/operators/null-conditional-operators)

~~~
jason0597
Well, Kotlin has it at least [https://kotlinlang.org/docs/reference/null-
safety.html](https://kotlinlang.org/docs/reference/null-safety.html)

~~~
mgkimsal
Groovy's had it forever, AFAICR.

~~~
oweiler
Groovy has the safe dereference operator but no notion of non-nullable types.

------
joemccall86
This would be immensely beneficial, and long overdue in my opinion.

~~~
hu3
Imagine how many thousand man-hours would be saved in debugging if this
existed a decade ago. Little things like this over time adds up.

------
EmpirePhoenix
Funny thing, some JVM's already do exactly this for many years:

Failed to write HTTP message:
org.springframework.http.converter.HttpMessageNotWritableException: Could not
write JSON: while trying to invoke the method
de.hybris.platform.catalog.model.CatalogModel.getId() of a null object
returned from
de.hybris.platform.catalog.model.CatalogVersionModel.getCatalog();

~~~
copperx
It would be useful to know what JVM is that.

~~~
michaelper22
Probably SAP's, since Hybris is an SAP product.

~~~
chopin
SAP's definitely does this, I just checked.

In fact I relied on it so much that I thought every JVM does this.

------
js8
I am not really that familiar with Java, but I don't understand how to get a
JVM heap dump if there is an exception that brings down the JVM or even a
thread. Is it even possible?

I work on mainframe (z/OS), and it is completely normal there that when an
application fails with exception (they're called ABENDs - from ABnormal
ENDing), you can get a dump of memory of the application. From that, you can
see all the values of the offending variables and all the relevant system
areas and so on.

~~~
jillesvangurp
You can turn this on but the dumps are kind of huge. Also, you can attach to a
running JVM and get a memory dump that way. NPEs don't bring down the JVM
though unless that happens in your main method of course, in which case the
process exits normally.

~~~
js8
What I mean, to produce the dump in the case of exceptions that percolate to
top level and exit the process or thread (such as NPE). I am aware that I can
attach, but that kind of defeats the ability to get the information in case of
unexpected error (in production). Dumps might be huge, but is that really a
problem on modern hardware? On z/OS this is doable (and common practice).

~~~
coldtea
> _What I mean, to produce the dump in the case of exceptions that percolate
> to top level and exit the process or thread (such as NPE)._

Why not have a top level try catch then?

~~~
js8
If you have a top level catch, then you lose the local context of the error
(local variables, parameters, stack trace). I mean seriously, this is a solved
problem on z/OS (the ABEND condition bypasses normal stack, so to speak), it
has been solved for over 50 years. I don't see why JVM couldn't do the same
thing - is it a technical problem? It seems like a purely cultural problem to
me.

~~~
deepsun
Maybe because in Java it's usually considered a very bad practice to halt your
program. All the web containers that run your code will catch any error, to
prevent bringing down the whole web service if only a small piece of it fails
(for example, cannot serve one URL, but can others).

So instead, in Java, it's your responsibility to catch and correctly handle
all the exceptions.

Also, variables in the last function (where exception was raised from) are
unlikely to give me understanding of the bug, because it's usually thrown
somewhere deep in others code, and also I'm more interested in _why_ it
happened, not just _what_ happened.

We could dump all the variables in the stack trace, but I'm not sure I would
like to read the whole dump of every variable in stack trace, that's just too
much. I'd better write my code with proper logging, so I would be able to read
the logs in order to understand not only _what_ happened, but _why_ it
happened. With good logs you rarely even need to debug anything, I'd say 95%
of the time it's pretty clear where to fix.

~~~
js8
Again, on z/OS, this is a solved problem. If the application runs inside an
"application server", for instance CICS, you can still get the dump and the
CICS can continue or even restart your app (or it can be restarted through
other things). You can get the dump of the process even if just one thread
will fail.

I know from experience on z/OS how useful full dump can be, and I sorely miss
it in Java. You wouldn't have to read the full dump yourself - a good
postmortem debugger can do that work for you - displaying stack trace, locals,
etc.

Good logs can certainly save you.. if you have them, and you happen to log
exactly what you need. But it's much easier to look at the memory and see what
went wrong. (Personally, I think logs are great on the boundary, to describe
inputs and outputs.)

I also think that the exceptions are kind of wrong approach (compared to z/OS
ABEND conditions), and it's not entirely true that failing loudly is a bad
practice, but that's for another discussion.

------
jayd16
Can we add column numbers to the stack traces? Seems like that would solve
this in the general case.

~~~
xxs
no, not really. Java Class format doesn't support columns, just lines.

~~~
TheRealPomax
Just imagine the request read "Can we add column numbers to the stack traces?
And to whatever code that relies on to add column numbers to stack traces?"

~~~
xxs
All the existing code and libraries have to be recompiled. Non-javac compilers
have to be updated, etc.

Pretty much all byte code editing tools would be obsolete as the constant pool
needs change and so on. Also the generated byte code would be more verbose
(worse load times)

The current change is easy to implement and it'd work on code compiled
for/with java 1.0.

------
_Codemonkeyism
After 20 years, first in Java and then in Scala I've been tortured by this
error message. Some years ago they fixed ClassCastException which for a long
time also didn't tell you what was wrong. There are some others if I remember
correctly with bad error messages, like NumberFormatException. In general
error messages on the JVM are very bad, wish they would take a look at Elm
error messages.

I've left this year the JVM for TS, but glad for everyone who uses the JVM.

~~~
hnthroaway1926
The default compiler options for java provide a lot of information on null
pointer exceptions. I'm sort of blown away that so many on this thread are
confounded by NPEs, in my view they are dead simple to track down _if_ you
have access to the source.

~~~
_Codemonkeyism
As I'm running a legacy larger Scala app, what would that compiler options be?
e.g. for the line

    
    
        service.withName(customer.getName().firstName);
    

with a NPE in line 25?

~~~
hnthroaway1926
Break out the line into multiple lines...

String s = customer.getName().firstName;

service.withName(s);

~~~
wtetzner
So the answer is rewrite your code to work around tooling deficiencies?

I'd prefer this be fixed in the JVM, so you can decide to write your code in
the way that's most readable, and not be forced to write it a certain way
because the tools suck.

------
phinnaeus
If nothing else, it is really interesting to see exactly why this is hard.

~~~
akerro
Historic reasons, Java used to be bloated so things like these were not
included, not computers are faster, have more memory and object metadata can
be expanded.

~~~
WrtCdEvrydy
> have more memory

The L in Java stands for Low Memory.

~~~
akerro
Nice joke from 2002

~~~
WrtCdEvrydy
Last time someone was let down by Java...

------
yxhuvud
Better than nothing, but worse than having a type system than disallows the
exception it in the first place.

~~~
tantalor
Maybe or optional types don't really help because your "get value" functions
also throw exceptions, or have undefined behavior, if the value doesn't exist.

Examples:

[https://en.cppreference.com/w/cpp/utility/optional/value](https://en.cppreference.com/w/cpp/utility/optional/value)

[https://docs.oracle.com/javase/8/docs/api/java/util/Optional...](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#get--)

~~~
fwip
The distinction here is that those implementations aren't a type system, they
are types in the existing c++/java type system.

In many languages that support Optional types as first-class citizens, it is a
compile-time error to ignore the fact that it may be null/none. Your code must
deal with this, and you won't have any surprise exceptions, even if you are
lazy.

~~~
Rebelgecko
Which languages? In the languages that I'm somewhat familiar that support
Optionals (Kotlin and Swift) you can still get NPEs, there's just some nice
syntax that makes it harder to do accidentally (e.g. by using !! or ! on a
null/nil optional)

~~~
timw4mail
Rust does not allow null values without resorting to unsafe code.

~~~
cesarb
Technically, it does: std::ptr::null and std::ptr::null_mut are not unsafe.
(Of course, you cannot try to dereference the resulting pointer without using
unsafe.)

------
gldev3
I don't know why i find this so funny but it's definitely something that would
be greatly appreciated!

------
edoo
It is almost 2020 so it is reasonable to know what the actual error is.

This reminds me of old C/C++ parsers that couldn't tell you what line was
expecting a bracket or semicolon and you got to manually find the parse error.

------
opportune
This is awesome! I ran into this exact issue yesterday evening trying to debug
a NPE on foo.bar().baz().hello()

IMO overhead should not even be a consideration. It doesn't make sense to
sacrifice detailed reporting just so someone can repeatedly handle NPEs as
part of their application's logic. I can't imagine a situation in which I
would look at code that throws enough NPEs for overhead to be a concern and
say "yes this is well designed code"

------
kazinator
This is too much of a hack; I predict it will be unreliable, at times sending
programmers on wild goose chases more time-wasting than an uninformed
investigation.

~~~
kbenson
Because of some track record of Java, or because you just have a feeling about
it?

I don't program in Java at all, and frankly, I was flabbergasted to see a
change as obvious and useful as this wasn't already in there a decade ago. Or
at least 3-4 years ago where a lot more languages started advertising better
compilation error messages.

~~~
cesarb
> Because of some track record of Java, or because you just have a feeling
> about it?

It's proposing to reverse-engineer the corresponding source code from the
bytecode when encountering the exception. That does sound like a potentially
unreliable hack.

~~~
repolfx
Not necessarily. The sort of dataflow analysis they're talking about is an
inherent part of bytecode verification already. A more advanced version of it
is done every time a class is loaded. JVM bytecode is designed to make this
sort of thing possible.

------
jolmg
I wonder if it's possible to add something similar to the protection you get
in Haskell to other languages via some static analyzer or something.

In Haskell, it's pretty much impossible to have unexpected nulls because the
ability to return nulls is something that is expressed in the type and needs
to be handled before working with the possible type to be able to pass the
type-check of the compiler.

For example, if I had a function:

    
    
      findFistOdd :: [Int] -> Maybe Int
    

That returned the first odd number in a list of numbers, the Maybe wraps the
return value so the result is either `Nothing` or `Just someNumber`. I can't
work with the result directly like

    
    
      1 + findFirstOdd [2,3]
    

It would fail the type check, because I need to tell the compiler what the
program should do if findFirstOdd couldn't find an odd number. (1 +) is
certainly not expecting a NULL as its argument type is Int. I can tell it,
"Just die if that happens":

    
    
      1 + (fromMaybe (error "couln't find odd number") $ findFirstOdd [2,3])
    

or I can tell it to work on the inside value if it's there, returning Just (1
+ someOddNumber) if it's there or Nothing if it's not:

    
    
      (1 +) <$> findFirstOdd [2,3]
    

Conversely, if a function says it returns an Int:

    
    
      addOne :: Int -> Int
    

That means it returns an Int, guaranteed. That's never going to be some null
value. It's not something you'd ever have to consider.

Kind of wish all languages were like this. Nulls are probably the cause of
most unexpected exceptions and it could be something that could always be
caught without even running the program.

~~~
oftenwrong
Optional is Java's impoverished version of Maybe:

[https://docs.oracle.com/en/java/javase/12/docs/api/java.base...](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/Optional.html)

Of course, an Optional reference can still be null because it's still Java.
There are also other implementations of Maybe-like types, and implementations
of Either-like types, etc in the Java ecosystem.

------
Myrmornis
> Given the bytecode, it is not obvious which previous instruction pushed the
> null value. To find out about this, a simple data flow analysis is run on
> the bytecodes.

This is a petty nit on what sounds like some great work but please do not use
the word "simple" in technical contexts like this. Having you assert that it
is simple does not make it easier for anyone to understand, and it is really
quite irritating. I don't even know what "data flow analysis" is and I've been
involved with computer programming and data analysis for 15 years.

~~~
cesarb
> I don't even know what "data flow analysis" is and I've been involved with
> computer programming and data analysis for 15 years.

You probably haven't been involved with compiler development in these 15
years; data flow analysis is one of the techniques used by optimizing
compilers to reason about the code. Given the context (not only there is a
compiler converting Java source code to Java bytecode, but also the Java
Virtual Machine acts as a "just-in-time" compiler from the Java bytecode to
machine code), everyone involved most probably already know what "data flow
analysis" is. And by "simple" the author most probably means that a basic data
flow analysis, with no extra bells and whistles, would be enough for the
described purpose.

------
hnthroaway1926
Interesting, so they want to go beyond the line number of the exception and
include information on what on the line is null.

It's sort of funny that their example doesn't follow the usual Java style
guidelines of encapsulating member variables in the parent and using set() and
get() methods for access.

a.to_b.to_c.to_d.num = 99;

This could be rewritten as a method in 'A' as...

protected void setDNum(int num) {

    
    
      C c = b.getC();
    
      D d = c.getD(); 
    
      d.setNum(99); 
    

}

In this case, a nullpointerexception would include the line number (using
default compiler options) clearly indicating which is null.

------
nickodell
>As computing the NullPointerException message proposed here is a considerable
overhead, this would slow down throwing NullPointerExceptions.

You could lower the overhead by not producing these messages if the exception
is thrown frequently. It would be similar to the existing
OmitStackTraceInFastThrow mechanism [0].

[0]:
[https://www.oracle.com/technetwork/java/javase/relnotes-1391...](https://www.oracle.com/technetwork/java/javase/relnotes-139183.html)

------
on_and_off
I don't see that many NPE in kotlin these days but pretty sure that on
java/Android I get a useful error message.

Is that an ART/Dalvik feature ?

~~~
m0skit0
I don't remember getting any useful error message, but I've been using Kotlin
for 3 years now, so I might not remember correctly. Can you post a stacktrace
of what you're referring to?

~~~
on_and_off
as stated in another comment, you get : s exception: Caused by:
java.lang.NullPointerException: Attempt to invoke virtual method
'java.lang.String java.lang.Integer.toString()' on a null object reference

the name of the method causing the crash (toString in that sample) + the line
number and location thanks to the stacktrace (although in some rare case it is
100% accurate, but still pretty close, maybe that's due to runtime inlining)
and it is very trivial to know where the exception comes from.

------
jimmaswell
Meanwhile, for as long as I can remember, Visual Studio could easily jump
straight to the offending null variable when debugging C#.

~~~
zahrc
There’s a difference between an IDE offering it, or the JDK

~~~
Gibbon1
The CLR gives you all that information. It's not just an IDE thing.

~~~
peteri
Still an open issue
[https://github.com/dotnet/coreclr/issues/25](https://github.com/dotnet/coreclr/issues/25)

The debugger in VS2017 does give the information, but I suspect thats just by
re-evaluating the expression.

------
IloveHN84
Java is pretty weird land. It's 2019 and there's still this issue with
Nullpointer Exception and the other big limitation is that you cannot create a
new instance of a generic type in a generic function, making programs dynamic,
like in C++. Funny, because the whole JVM is based on C++

------
rb808
I'm a bit torn on this because I always avoided having too many method calls
on the same line. Code Complete recommended this a long time ago.

However now I see just about everyone stacking this method calls one after the
other. Is this just acceptable now?

state = stateFromPostcode(customer.getAddress().getPostCode());

~~~
EpicEng
Depends really. Can getAddress() return null or is it guaranteed to be there
per your system design? If it's not then that code is wrong, but if you know
your invariants and enforce them higher up the chain then perhaps not.

I realize that all sounds very obvious, but like most things... it really does
depend. It can certainly make debugging harder if you need to step through.
Personally I try to write code which simply doesn't allow nulls below the
first layer they may be encountered. Not always possible though (e.g. nullable
fields in entities and whatnot.)

~~~
nradov
This is where annotation based null analysis can really help.

[https://dzone.com/articles/eclipse-annotation-based-null-
ana...](https://dzone.com/articles/eclipse-annotation-based-null-analysis)

------
_ZeD_
this is good stuff. please come back 10 years ago and do it!

~~~
sisu2019
I am so frustrated at the slow pace of java but then I remember that for some
reason everyone is using JS now and laugh in at-least-some-semblance-of-
typesafety (also stacktraces, a module system not invented by a SF hobo, a
real compiler and build systems that aren't a complete disaster)

------
4thaccount
I don't write Java, but support an application written in Java that gets null
pointer issues and aborts all the time. It's also annoying that there is
precious little information for me to go on.

~~~
chapium
It generally means that an object was supposed contain data, but it doesnt.
This typically could mean the network disconnected or a file was not read. You
could be missing .jar files that contain the appropriate classes. It can also
be a programmer mistake.

Its not a very clear error, but you can narrow down the issue to a few things
even when doing application support.

~~~
4thaccount
In our case it is reading from an antiquated non relational database and
usually one of the millions of fields that were being parsed had a "null"
value. It is easy to fix, but only if I know which ones the app needs.

~~~
chapium
sounds rough

------
goshx
Finally someone noticed it!

------
rainhacker
But they are still calling it NullPointerException, not
NullReferenceException. I guess this change is too invasive

~~~
TheRealPomax
Only a little: it would be a backwards incompatible change affecting pretty
much all production java code ever written, requiring everyone on the planet
spend time and money on uplifting all their code for what is really just a
cosmetic change =)

------
mark242
Better idea:

    
    
      Optional.ofNullable(foo).orElse(new Foo())

~~~
deepsun
In kotlin: `foo ?: Foo()`

Although I'd rather creating objects if not needed to be easier on memory.

~~~
erik_seaberg
In Java that's

    
    
      Optional.ofNullable(foo).orElseGet(Foo::new)
    

to avoid side effects from building a throwaway object.

------
portal_narlish
JEP draft: Remove NullPointerException from language

------
kords
hopefully we'll see the same thing in "Object reference not set to an instance
of an object" exceptions in .NET

------
danra
I hope I'm not the only one who read the title as adding some helpful text to
the exception for n00bs, which explains about the very concept of null.

------
jackvezkovic
That moment when you step out of your safe Scala world just to realize that
people still face NPEs.

~~~
darksaints
I write exclusively in Scala, but NPEs still exist. The moment you introduce a
java library to your code, you become vulnerable to them. And if you have an
inexperienced java developer switching over to scala, you'll get nulls in
scala code as well.

And in performance critical code you might find them as well. Sometimes
dereferencing an option is too costly, so you put up with an anti-pattern in
order to get lower latency or higher throughput.

------
jtdev
Other than supporting and working with legacy software, I cannot muster any
reason to write code in Java at this point... it’s just a terrible language to
work with.

~~~
oftenwrong
Java sucks in a lot of ways, but the reasons why it sucks and the methods for
mitigating the parts that suck are well-known. On the plus side, you get an
ecosystem with high-quality libraries and tooling that actually come with
documentation and examples, you get a good-enough type system to allow
reasonably safe and strict development, and you can easily find solutions for
any pitfalls you fall into because it's likely 1000+ people have already done
the same. As a member of the
[http://boringtechnology.club/](http://boringtechnology.club/), I embrace
Java's warts and enjoy its usefulness.

My advice:

\- avoid frameworks like the plague

\- write in a functional style

\- lean on the type system as much as possible

\- don't chase the trendy stuff

\- avoid annotations

\- use code generation

