When writing Java servers (or servlets that run in a container), I’ve learned a few things that have saved me a bunch of trouble during development, as well as later on.
Injection: if something is singleton (request, global or any other scope), the only things injected to it should also be singleton – otherwise you get implicit singletons of things the singleton holds, and may not be what you want – and generally harder to reason about. Better to have the singleton instances be leaves in the tree of injected things.
Injection: if not singleton, ctor’s should only be assignments/precondition checks. Keeps them cheap to produce. Things like caches and other persistent-ish state should be singleton on their own and injected into the things that need them, rather than the object holding the cache being singleton. Also keeps things easier to reason about.
Immutability is a good thing! The hype is not misplaced. Uses Google Guava’s Immutable classes. Note that
ImmutableBlah.copyOf(anImmutableObjOfSameType)doesn’t actually copy, but returns the copied object.
Avoid having mutable data escaping a function. It can be just fine to use mutation inside when it increases clarity like when building complicated structures where you may have to reference the contents of it while still building it. But return an Immutable copy or an Unmodifiable wrapper of the object. But remember if you use an Unmodifiable version, that if the underlying object can still be mutated, the contents can still change. Personally, FWIW, I stick to the Immutable collections.
finalfor all variables and method parameters, where practicable (i.e. in a normal
for (int i = 0....loop,
icannot be final). Single assignment is good. Then lack of a
finalbecomes a strong signal that the variable is going to be changed – it would be nice if variables were
finalby default though.
Immutablility can make something that might’ve been a nightmare much simpler if/when you need to parallelize things. Knowing you can trust the world to not change under you is a very comforting thing.
Java Language Things
- Anonymous blocks can be good. Some methods really are easier to read if they are long (no excuses however for those that need not be) and this can help to control scope and make things easier for the reader. For example:
1 2 3 4 5 6 7 8 9 10 11
In this example, the only things that escape the scope is
other. Also, with it declared before the block like this, it’s a
signal to the reader what the intent is. It also can sometimes
eventually lead to method extraction.
Follow the advice on flow control
Optionalmakes you have to explicitly deal with the case where the object isn’t there (which is what you want).
Optional.mapcan be really quite nice when performing a series of operations against an
Optionalwithout having to manually do
Avoid inlining intermediate value expressions e.g.
could be inlined to:
The former allows you to easily hit it in the debugger, the latter not so much. Also when exceptions are reported the former will give you a better idea at where it failed.
Related: if Java cannot inference the type of a lambda, don’t cast inline, just make a new variable with the proper type. It just reads easier for future you (see below).
Use enums instead of booleans. The name of the enum tells you more specifically what something means. Even if it’s just a two valued enum, it can often save you the trouble of finding the method signature to figure out what the boolean means.
For constructors with lots of arguments, strongly consider a builder. Java unfortunately doesn’t have keyword args, which would make it simpler to understand, so builder-pattern it is. BTW: both eclipse and IntelliJ have plugins or built-in features available for generating builders.
1 2 3 4 5
go-like pattern if it becomes pervasive, especially if there are
multiple levels of function calls that have to do this. Create a
Exception that you can catch at the top level and
extract the return value from. If Java had proper continuations that
would avoid the problem, but alas, not yet.
- Lambdas and method references are a good thing. Use them.
Listen[ing|able]varieties of futures and executors are good things. Use them. The first time you use
Futures.allAsList(listOfFutures).get()(amongst other similar things), or
Futures.transformyou’ll thank me. Also see Spotify’s Trickle Library.
Give your executors names. Tracing through logs is made much simpler when the entries have their thread names logged with them.
Less Magic, Mo’ betta
Example: slf4j – if you’ve ever had to spelunk around to figure out
which underlying log library is being used,
slf4j is a refreshing
change. No autodetection, just include the backing jar file.
Magic is just something hard to debug through later when it doesn’t work. This is why I will only choose things like Spring, Guice, Hibernate, JPA, Apache Common Logging and similar things if they are forced on me.
Write code with the idea that you’ll have to deal with things when they break. Something that “looks nice” may be a real pain to debug. A number of the above tips relate to this point. “Elegant” code can be a real bear to debug through – it’s usually just not worth it.
Keep Future-you in Mind
Present-you may fully understand how everything you need to know works. Future you may not. Code for Future-you and Future-you will thank you. If not for Future-you, for Future-somebody-else.
Putting some of the bits together
Now, just sit back and thing about what this might look like without some of the above things I mentioned…
splitQuery returns an object with two
containing queries to run. So for both the this and that queries, the
.map business submits callables to the executor to execute the
do[This|That]Query methods) and then adds the futures
for those to the
futures list, which then is all joined up at the
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20