Virtual Developer Workshop: Containerized Development with Docker
|Bruce Eckel's Thinking in Java||Contents | Prev | Next|
the first letter of class names. The first letter of fields, methods, and
objects (handles) should be lowercase. All identifiers should run their words
together, and capitalize the first letter of all intermediate words. For example:
Packages are a special case: they are all lowercase letters, even for intermediate words. The domain extension (com, org, net, edu, etc.) should also be lowercase. (This was a change between Java 1.1 and Java 1.2.)
- When creating a class for general-purpose use, follow a “canonical form” and include definitions for equals( ), hashCode( ), toString( ), clone( ) (implement Cloneable), and implement Serializable.
- For each class you create, consider including a main( ) that contains code to test that class. You don’t need to remove the test code to use the class in a project, and if you make any changes you can easily re-run the tests. This code also provides examples of how to use your class.
- Methods should be kept to brief, functional units that describe and implement a discrete part of a class interface. Ideally, methods should be concise; if they are long you might want to search for a way to break them up into several shorter methods. This will also foster reuse within your class. (Sometimes methods must be large, but they should still do just one thing.)
- When you design a class, think about the client programmer’s perspective (the class should be fairly obvious to use) and the perspective of the person maintaining the code (anticipate the kind of changes that will be made, to make them easy).
to keep classes small and focused. Clues to suggest redesign of a class are:
1) A complicated switch statement: consider using polymorphism
2) A large number of methods that cover broadly different types of operations: consider using several classes
3) A large number of member variables that concern broadly different characteristics: consider using several classes
- Keep things as “ private as possible.” Once you publicize an aspect of your library (a method, a class, a field), you can never take it out. If you do, you’ll wreck somebody’s existing code, forcing them to rewrite and redesign. If you publicize only what you must, you can change everything else with impunity, and since designs tend to evolve this is an important freedom. Privacy is especially important when dealing with multithreading – only private fields can be protected against un- synchronized use.
- Watch out for “giant object syndrome.” This is often an affliction of procedural programmers who are new to OOP and who end up writing a procedural program and sticking it inside one or two giant objects. With the exception of application frameworks, objects represent concepts in your application, not the application.
- If you must do something ugly, at least localize the ugliness inside a class.
- Anytime you notice classes that appear to have high coupling with each other, consider the coding and maintenance improvements you might get by using inner classes (see “ Improving the code with an inner class ” on page 759).
- Use comments liberally, and use the javadoc comment-documentation syntax to produce your program documentation.
- Avoid using “magic numbers,” which are numbers hard-wired into code. These are a nightmare if you need to change them, since you never know if “100” means “the array size” or “something else entirely.” Instead, create a constant with a descriptive name and use the constant identifier throughout your program. This makes the program easier to understand and much easier to maintain.
- In terms of constructors and exceptions, you’ll generally want to re-throw any exceptions that you catch while in a constructor if it causes failure of the creation of that object, so the caller doesn’t continue blindly, thinking that the object was created correctly.
- If your class requires any cleanup when the client programmer is finished with the object, place the cleanup code in a single, well- defined method with a name like cleanup( ) that clearly suggests its purpose. In addition, place a boolean flag in the class to indicate whether the object has been cleaned up. In the finalize( ) method for the class, check to make sure that the object has been cleaned up and throw a class derived from RuntimeException if it hasn’t, to indicate a programming error. Before relying on such a scheme, ensure that finalize( ) works on your system. (You might need to call System.runFinalizersOnExit(true) to ensure this behavior.)
- If an object must be cleaned up (other than by garbage collection) within a particular scope, use the following approach: Initialize the object and, if successful, immediately enter a try block with a finally clause that performs the cleanup.
- When overriding finalize( ) during inheritance, remember to call super.finalize( ) (this is not necessary if Object is your immediate superclass). You should call super.finalize( ) as the final act of your overridden finalize( ) rather than the first, to ensure that base-class components are still valid if you need them.
- When you are creating a fixed-size collection of objects, transfer them to an array (especially if you’re returning this collection from a method). This way you get the benefit of the array’s compile-time type checking, and the recipient of the array might not need to cast the objects in the array in order to use them.
- Choose interfaces over abstract classes. If you know something is going to be a base class, your first choice should be to make it an interface, and only if you’re forced to have method definitions or member variables should you change it to an abstract class. An interface talks about what the client wants to do, while a class tends to focus on (or allow) implementation details.
- Inside constructors, do only what is necessary to set the object into the proper state. Actively avoid calling other methods (except for final methods) since those methods can be overridden by someone else to produce unexpected results during construction. (See Chapter 7 for details.)
- Objects should not simply hold some data; they should also have well-defined behaviors.
- Choose composition first when creating new classes from existing classes. You should only used inheritance if it is required by your design. If you use inheritance where composition will work, your designs will become needlessly complicated.
- Use inheritance and method overriding to express differences in behavior, and fields to express variations in state. An extreme example of what not to do is inheriting different classes to represent colors instead of using a “color” field.
- To avoid a highly frustrating experience, make sure that there’s only one class of each name anywhere in your classpath. Otherwise, the compiler can find the identically-named other class first, and report error messages that make no sense. If you suspect that you are having a classpath problem, try looking for .class files with the same names at each of the starting points in your classpath.
- When using the event “adapters” in the Java 1.1 AWT, there’s a particularly easy pitfall you can encounter. If you override one of the adapter methods and you don’t quite get the spelling right, you’ll end up adding a new method rather than overriding an existing method. However, this is perfectly legal, so you won’t get any error message from the compiler or run-time system – your code simply won’t work correctly.
- Use design patterns to eliminate “naked functionality.” That is, if only one object of your class should be created, don’t bolt ahead to the application and write a comment “Make only one of these.” Wrap it in a singleton. If you have a lot of messy code in your main program that creates your objects, look for a creational pattern like a factory method in which you can encapsulate that creation. Eliminating “naked functionality” will not only make your code much easier to understand and maintain, it will also make it more bulletproof against the well-intentioned maintainers that come after you.
- Watch out for “analysis paralysis.” Remember that you must usually move forward in a project before you know everything, and that often the best and fastest way to learn about some of your unknown factors is to go to the next step rather than trying to figure it out in your head.
- Watch out for premature optimization. First make it work, then make it fast – but only if you must, and only if it’s proven that there is a performance bottleneck in a particular section of your code. Unless you have used a profiler to discover a bottleneck, you will probably be wasting your time. The hidden cost of performance tweaks is that your code becomes less understandable and maintainable.
- Remember that code is read much more than it is written. Clean designs make for easy-to-understand programs, but comments, detailed explanations, and examples are invaluable. They will help both you and everyone who comes after you. If nothing else, the frustration of trying to ferret out useful information from the online Java documentation should convince you.
- When you think you’ve got a good analysis, design, or implementation, do a walkthrough. Bring someone in from outside your group – this doesn’t have to be a consultant, but can be someone from another group within your company. Reviewing your work with a pair of fresh eyes can reveal problems at a stage where it’s much easier to fix them and more than pays for the time and money “lost” to the walkthrough process.
- Elegance always pays off. In the short term it might seem like it takes much longer to come up with a truly graceful solution to a problem, but when it works the first time and easily adapts to new situations instead of requiring hours, days, or months of struggle, you’ll see the rewards (even if no one can measure them). And there’s nothing that matches the feeling that comes from knowing you’ve got an amazing design. Resist the urge to hurry; it will only slow you down.
can find other programming guidelines on the Web. A good set of links can be