Wednesday, March 11, 2009

Book review: Effective Java

This review covers Effective Java 2nd ed., by Joshua Bloch.  Stoughton, Massachusetts: Addison-Wesley (an imprint of Pearson Education, Inc.), 2008.

Summary

From the Foreword:

If you have ever studied a second language yourself outside the classroom, you know that there are three things you must master: how the language is structured (grammar), how to name things you want to talk about (vocabulary), and the customary and effective ways to say everyday things (usage)… This book addresses your third need: customary and effective usage.

And as Bloch says in the Introduction:

This book consists of seventy-eight items, each of which conveys one rule.  The rules capture practices generally held to be beneficial by the best and most experienced programmers.

The items are organized into ten categories:

  • Creating and Destroying Objects
  • Methods Common to All Objects
  • Classes and Interfaces
  • Generics
  • Enums and Annotations
  • Methods
  • General Programming
  • Exceptions
  • Concurrency
  • Serialization

The format of the book is based on Scott Meyers’ Effective C++.

The book is designed as a next-level book for those who already have some experience with the Java programming language rather than as an introduction to the language.

The Good

The book:

  1. Helps the Java non-expert toward usability, robustness, and flexibility.  There are a lot of things to know about Java to use it well.
  2. Gives reasons for his statements. In cases where you may decide to deviate from Bloch’s advice, this gives you the opportunity to examine his reasoning and understand the implications of your decision, so you’re not flying blind.
  3. Makes recommendations concrete by giving examples. For example, in Item 20, the exhortation to prefer class hierarchies to tagged classes is illustrated with a tagged class and its alternative representation as a class hierarchy.  There are helpful code examples like this sprinkled throughout the book.
  4. Includes a succinct summary of the advice at the end of many Items.
  5. Provides expert help in doing common but tricky tasks. For instance, the naive developer may not know the contract that they’re responsible to uphold when overriding the equals() method (Item 8), or may not realize the maintainability implications of marking an object Serializable (Item 74).  Much time and distress can be saved by reading about common pitfalls ahead of time.
  6. Points out how to avoid many surprises. For example, several important details that should be dealt with when making a class Clonable (and the consequences of ignoring them - Item 11); and details about the relationship between arrays and generics (Item 25).
  7. Provides help in doing some advanced tasks well. For example, the chapter on concurrency points out the benefits and details of using the Executor Framework in java.util.concurrent rather than the Thread class (Item 68); the Serialization chapter presents the Serialization Proxy pattern (Item 78);  and Item 17 discusses issues that should be addressed when designing a class for inheritance.
  8. Includes some just plain old great ideas. The book includes several ideas that made me think, “Huh!  Why aren’t we doing this?”  An example is his advice to put methods on your checked exception that help your callers extract the information they want out of it without having to resort to parsing the description string (p. 245) and to give your custom exception types constructors that receive that information (p. 254).
  9. Is honest about Java design mistakes. Bloch believes in the Java platform, but he is not shy about pointing out its warts.  For example, p.86: Stack should not extend Vector and Properties should not extend HashTable.  I appreciate that honesty.
  10. Helps you design for robustness. Don’t leave room for error where you can prevent it — Bloch gives many techniques for making correct use happen naturally.
  11. Is scientifically up-to-date: Doesn’t call Pluto a planet (p.149) : )
The Bad
  1. Often doesn’t take testability into account. This is my number one gripe with the book.  Examples:
    • Item 13, “Minimize the accessibility of classes and members”, doesn’t address how its advice interacts with making a class unit testable.  In practice, there tends to be some tension between minimized accessibility and full testability.
    • Item 17, “Design and document for inheritance or else prohibit it”, similarly recommends marking classes final when they’re not designed for inheritance.  If you follow this advice naively, though, you could be setting up your code to be untestable (see the section in Michael Feathers’ Working Effectively with Legacy Code chapter 10, titled “The Case of the ‘Helpful’ Language Feature”).  Bloch does mention in passing an alternative that “provides the flexibility to use subclasses internally” (p.91), so if you have your testability goggles on that could point you in a helpful direction.
    • Item 60, “Favor the use of standard exceptions” - but it can be quite helpful to extend the standard exceptions so that test code can be certain that when an exception occurs, it was thrown by the place the test is testing.
  2. Sometimes bogs down in the details. Examples are Item 11 on cloning and Item 30 on enums, both of these being several pages longer than their sibling Items.  There were maybe five such extra long Items.
  3. Lacks numbered subheadings under Items. The book makes do with bold text, but it would be more navigable if there were numbered subsections.  This is especially the case for the longer Items.
  4. Could benefit from flowchart diagrams. Several times near the end of a chapter there is a series of questions leading to a decision tree.  For example, on p. 180:

    If you answered no to the first question, ask yourself one more: Do I want to limit the use of this marker to elements of a particular interface, forever? If so, it makes sense to define the marker as a subinterface of that interface.  If you answered no to both questions, you should probably use a marker annotation.

    It takes effort to process this prose, and I would find it helpful if such conditional logic were presented in the form of some kind of flowchart-like diagram.

  5. Directing attitude. This is the flip side of “helps you design for robustness” — not uncommonly, in the quest to close the gaps where a class could be used improperly with poor results, Bloch’s advice is to prevent, guarantee, or force.  (An example of this type of advice is Item 58, where the recommendation is to use checked exceptions to force clients to deal with an exception.   I’m not sure this is A Good Thing.)
Overall

I’ve used the word “details” a lot in my descriptions.  Bloch gives you the details you need to use Java effectively.

By reading this book I became aware that there’s more to understand about Java than I realized.  There were several things I didn’t know I didn’t know — the caveats of serialization and cloning, for instance — and some things I knew I didn’t know (concurrency comes to mind)

My biggest concern is the lack of attention given to testability.  The reader who cares about testability is on their own to work through which advice can be taken at face value and which must be modified by testability concerns.

I highly recommend this book.  Its imperfections are surmountable, and every professional Java developer should understand the issues it addresses.  Even in the cases where you  may not follow Bloch’s rules,  you will gain valuable understanding of the trade-offs you’re making; and you’ll avoid many costly mistakes.

No comments:

Post a Comment