A few weeks ago I posted a software developer’s reading list. An important criterion for this list was technology independence because most software development skills transcend whatever tools we are currently using. That being said, our choice of tools is quite important. This article explains why I currently build software using Java (and its platform). I also describe how I write Java: techniques, conventions, and practices.
I’m no Java evangelist. I am frustrated by the gap between established languages and what I believe our profession is capable of designing. (One cause is the momentum of standards which makes them expensive to replace.) For me the goal of software development is to evolve valuable software with a team. I think Java, with its platform & ecosystem, is currently the best tool to support this multi-faceted goal. (This applies to general application programming, not to programming requiring the ability to explicitly manage memory.)
Related Article: Teach yourself programming in 10 years
Why I Write Java
I use Java for general application programming. I recommend Python or Ruby for learning programming. I don’t know what to use for programming requiring the ability to explicitly manage memory (i.e. C, C++, D, Rust).
Benefits of Java
- Platform Maturity: Java is stable and it’s about as fast as you’re going to get with garbage collection. It is depended on by huge companies. I want to spend time evolving valuable software, not fighting with semi-mature tools. (Examples of being forced to switch back to Java include Twitter and Yammer. Notice the dates on these, because issues do get ironed out.)
- Statically Typed: I’ve used Python professionally for 7+ years. It is a wonderful language that has taught me a lot. However I am convinced that static typing is a profoundly valuable tool for software development. Reasons include:
- Contextual Documentation: With static typing I always know the types for inputs and outputs. I know exactly what I have to provide as input, and I know the attributes available on the output. This makes it a little easier to work with code I remember, considerably easier to work with code I can’t remember, and a ton easier to work with code I’ve never seen before (i.e. learning the AWS SDK for the first time).
- Explicit Interfaces: Object-Oriented Component Design is about managing interfaces. With explicit interfaces I can more easily focus on interface design without worrying about the implementation details (classes). When I do get to implementing a class, I can declare what interfaces it is supposed to implement, as well as the interfaces of the dependencies. These declarations are great documentation and are used by tool support to help me.
- Tool Support in an IDE: Tool support makes programming easier so I can focus more on higher level issues. Examples of how it helps include:
- When an interface changes, the out-of-sync clients and implementations go red.
- I have confidence that the automated rename refactoring works. If it doesn’t work, I expect something to go red.
- If I want to implement an interface, I can auto generate a class with all the stub methods. Then I can write fields on the class and auto generate a matching constructor. (This last part sounds possible with dynamic typing + conventions.)
- I can call a non-existing method on an object and the call goes red. Then I auto generate a corresponding method on that object’s interface. Then that object’s class goes red because it doesn’t implement that method yet. Then I auto generate a corresponding method stub on the class.
- Prevent Type Errors: With static typing I discover type errors instantly instead of finding them when running tests, or even later if I don’t have perfect test coverage. I find rapid prototyping to be a easier when I have static types to lean on. When I was switching from Python to Java, I found I could throw together working code faster with Java, despite Java’s annoying verbosity and despite knowing Python much better.
- Write Once, Run Most Places: With Java I have one language that can target servers, Android, and browsers via Google Web Toolkit (GWT). Commenters have suggested that J2ObjC and RoboVM can help reach iOS. If you need explicit memory management, then you’ll unfortunately need a different language. I believe that fragmenting a team’s codebase into multiple languages introduces a tremendous amount of accidental complexity and redundancy. I think the non-linear costs significantly outweigh the relative advantages of various languages. Complexity is the enemy, and unnecessary variation is a significant cause of complexity. (For an alternative perspective, look into polyglot programming: a website, a thesis.)
- Dependency Packaging: I really like being able to package all the dependencies into a JAR or WAR file for deployment. Other languages obviously have solutions to this, but I’ve found Java’s solutions to be at least easier than Python’s virtualenv.
For an alternate perspective, Michael O. Church wrote a nice 2010 essay about the benefits of static typing. In 2013 he wrote an article about why he has softened his static typing stance and has found Clojure to be great.
Problems with Java
The cons are mostly language design issues:
- Insufficient Type Inference: This is inane. Java 7′s diamond operator helps some, and Guava Collections Utilities provided some of its benefits for Java 6 (using existing inference for generic methods). There are other problems with the type system, most notable being the inclusion of null (Guava’s Optional helps with this).
- Only One Superclass/Mixin/Trait: Implementation inheritance causes problems. Multiple implementation inheritance causes even more. I prefer to instead use composition when possible. However without syntactic support to delegate to nested objects, you do want multiple mixins sometimes.
- Verbose Lambda Syntax: Java 8′s lambda should fix this.
- No Get/Set Properties: I want to be able to use “x.y” and “z = x.y” while preserving uniform access. This is available in at least C#, Python, and Ruby.
- XML Configuration: I prefer to use code files for configuration. If it must be a separate language, then YAML would be better. Of course XML isn’t inherently part of Java, but it is used by the community in practice. (For example, I have to wrestle with Maven XML files.) (These comments say the community is trending towards less XML: comment 1, comment 2, comment 3.)
- C Style Syntax: I prefer significant whitespace, like Python. This is just a personal preference.
- CamelCase: I prefer snake_case, because _ reads more like a space, which is how we usually separate words. This is just a personal preference. (I think the best choice would be to do auto conversion between snake case and camel case based upon the programmer’s preferences and needs. It could then be an IDE setting for how to render the code. You’d have to force CamelCase and snake_case to be mutually exclusive, so this is probably only possible for a new language.)
- … I’m sure this list could be arbitrarily long, but you get the idea …
Why I Don’t Use <other language>
Here are a few reasons why I currently choose Java over other languages, in addition to the fact that many of them can’t target browsers. (I include these notes in order to explain my thought process, as I would to a friend. Please don’t beat me up over it. Describing a language in a sentence or two is absurdly inadequate. I’m just trying to convey a brief sense of my current general conclusions, in case it is helpful. I could, and maybe should write a post about what excites me about many of these languages.)
- C#: I don’t use C# because I’m not interested in Microsoft’s ecosystem (i.e. VisualStudio, Azure). But if you are, C# seems like a good alternative to Java. C# was originally Microsoft’s answer to Java. In some areas it has more gained more advanced features (comparison of features). I particularly like LINQ. I think it’s cool that they hired Haskell folks like Eric Meijer and SPJ.
- Scala: Scala is probably the language with the closest design to what I’d want. Building on the JVM puts it on the “fast” track to maturity, but I don’t feel like it is there yet. Here is a dialogue about issues that might arise in practice. (Comments about Scala’s current maturity: slides, comment 1, comment 2, at Netflix, at Coursera.)
- Clojure: Clojure is innovative, but it is dynamically typed. I don’t know about the maturity of the platform, ecosystem, and libraries, but here is are some comments: comment 1, comment 2. Clojure’s explanation of state and mutation is great. The language seems particularly innovative in the area of modeling shared memory. Rich Hickey has given some great presentations. ClojureScript is of course interesting to me. In my heart I feel that Lisp syntax hinders readability and macros are too fancy, but having read amazing Lisp books, I wonder if I’m a bit ignorant to believe that.
- Python: Dynamically typed. Platform and libraries are less mature than Java.
- Ruby: Dynamically typed. Platform and libraries are less mature than Java.
- Perl: Dynamically typed and worse design than Python and Ruby. (Of course worse than the best does not mean bad. Like many people, Perl was my first scripting language, and it really opened my eyes. I would be worse at writing Java if I hadn’t experienced Perl and Python.)
- PHP: Dynamically typed and worse design than Python and Ruby. (I think software is valuable if it is used, and by this metric PHP has been very valuable. It isn’t the best for my needs though.)
- Dart: Not mature enough. Like TypeScript its goals and corporate backing make it interesting to watch.
- TypeScript: Not mature enough. Like Dart its goals and corporate backing make it interesting to watch.
- Go: I’ve only looked at the docs. The ecosystem maturity is my primary concern, but like Dart and TypeScript, it is interesting to watch.
- Haskell: Insufficient adoption and difficult to work with. Real World Haskell is worth reading, but I don’t find Haskell code particularly readable. Haskell does not appeal to me as a software developer who loathes “tricky” code. (These are just my impressions, don’t take my word for it.) That being said, Haskell is motivated by numerous unassailable insights and benefits. Languages should make immutable the default and should strongly encourage (but not require) writing wide swaths of referentially transparent pure code. You can and should blend FP with your OO design.
- F#: Other than the idea (FP on .NET), I don’t know much about it in practice. Here is someone’s comment.
- Groovy: Other than the idea (scripting on JVM), I don’t know much about it in practice.
- Objective-C: Haven’t looked into it much, but I will when I write software for iOS.
- Other Languages: There are many languages with communities that are too small for me to be comfortable using them on professional projects: OCaml, ML, Common Lisp, Scheme, Smalltalk, etc. Obviously not everyone shares this opinion. Also, how will a community get bigger unless someone is willing to be the tip of the spear?
- Programming with explicit memory management: This bucket includes C, C++, Rust, D, etc. I haven’t needed to explicitly manage memory, so I haven’t used these professionally. (Other than C, C++ and assembly as a student and a teaching assistant.)
How I Write Java
One of the concepts on my software developer’s reading list is Code Design: Writing intention revealing code. My starting point for how to write clean Java is two excellent books: Clean Code and Effective Java (2nd edition).
In addition to the material in these books, here are other practices I follow when writing Java…
Dependency Inversion & Type Naming Conventions
Dependency Inversion means having your dependencies provided to you, rather than statically importing them or building them yourself. This increases modularity and is important for testing. Ideally you statically import (depend on) interfaces, not classes or singletons. I think this is why the first suggestion in Effective Java is to use static factory methods. Here is an example of the organization and naming I use to follow this advice:
- Employee (public interface): The employee interface. I prefer this over the IEmployee convention.
- EmployeeClass (package visibility class): The employee class. (The implementation.) I prefer this over the EmployeeImpl convention.
- EmployeeLib (public abstract class): The library for creating instances of Employee. I prefer this over the plural Employees convention, but not by much. This library contains the static factory methods, as well as other related miscellaneous functionality. If there are multiple factory methods, only one uses “new”, and the others delegate to it. I prefer having a separate file for the EmployeeClass rather than nesting it inside the EmployeeLib.
- PersonMixin (public abstract class): A shared superclass. Even though I prefer composition over inheritance, implementation inheritance is useful for quick behavior reuse or implementing the Template Method Pattern.
If you strictly follow these conventions you will never have the words “public class” in your codebase (be pragmatic though). Notice that if a CompanyClass statically imports EmployeeLib to create an Employee instance, then that isn’t quite dependency inverted yet. This instance isn’t provided, it is retrieved. However it would be easy to invert this dependency by instead passing an Employee instance or Employee factory into the CompanyClass constructor (or using some other form of Dependency Injection).
Here’s a related Google Presentation: Don’t Look for Things.
Other Practices I Follow When Writing Java
Following a company or team standard is actually more important than what exact standards you choose to follow. Without shared standards you introduce unnecessary variation (complexity) into the codebase. Here are some of my current practices:
- Use Google Guava as a Java extension (user guide, philosophy, compared to Apache commons).
- Use jUnit for unit tests, using annotations and using the assertThat() syntax with Hamcrest matchers. TestNG seems to be a popular alternative to jUnit (list of frameworks).
- Use Mockito for mocking (but I prefer to use fakes when possible).
- Use Maven for building. I find it difficult to use at times but it is the standard. I put a lot of links into my pom.xml files because because I find POM to be a collection of magical incantations (here’s an example) .
- I use Eclipse as an IDE, but I don’t know enough about the alternatives to make a recommendation. I use Google’s Eclipse plugin for GWT. IntelliJ IDEA appears to be a well liked alternative to Eclipse. (I’ve definitely noticed an enthusiasm gap between Eclipse and IntelliJ in favor of IntelliJ. I haven’t tried it yet though.)
- Use Google’s Eclipse Java Formatter to follow Google’s Style Guide. (discussion)
- Have the IDE require @Override annotations.
- Use Guava’s Optional to avoid using null (Sir Tony Hoare’s billion dollar mistake).
- Make objects immutable by default. Guava’s immutable collections help. If an immutable object hashes based on state instead of memory address, then it is called a value object. Among other benefits, immutable objects remove an entire category of bugs and complexity.
- I wish “final” was the default so you’d have to mark variables as “mutable”. But should we use final for all immutable variables? (Which is most of them for me.) This is a decision of communication and enforcement vs. cluttering the code. For class fields, I think using final is definitely worth it because any method on the class could potentially mutate a field. For method inputs and local variables, I don’t think writing final is necessary. Writing final for these local variables doesn’t seem to be a favorable tradeoff because my methods tend to be short.
- Try to write referentially transparent functions. These functions don’t have side effects and their output is simply a function of input, not when the function was called. In functional languages like Haskell this kind of pure code is the default. In Java it is not the default, but it should be our default in practice. Among other benefits, referentially transparent code removes an entire category of bugs and complexity.
- Prefer composition over implementation inheritance. I think this is a general consensus.
- Keep things small: methods, classes, and directories.
- Keep the numerous component design principles in mind.
- Try to apply modifiers in this order: public protected private abstract static final strictfp (discussion 1, discussion 2)
- Never have public instance fields (always use getters/setters).
- Use java.util.Objects.requireNonNull to defensively receive input. Guava has other preconditions.
- Use Guava’s toStringHelper for implementing toString() and java.util.Object.hash() for implementing hashCode()
- Avoid accidental ordering by preferring Set and Map over List. This removes a class of ordering/duplication bugs. There is a (small?) performance tradeoff, but most code isn’t in a bottleneck. (Here’s a discussion about this.)
- Pragmatically adapt to context instead of dogmatically adhering to context-free rules such as these.