Meine Best Practices für Java- & Webentwicklung
- Die Paradigmen
- Meine Best Practices – Allgemein
- Meine Best Practices – Java
- Meine Best Practices – JavaScript
- Meine Best Practices – CSS
- Best Practices aus "Effective Java (3rd Edition)"
Best Practices aus "Effective Java (3rd Edition)"
Exzerpt aus "Effective Java (3rd Edition)
" by Joshua Bloch
1. Return empty collections or arrays, not nulls
Wenn die Methode ein Set zurückgibt, kein return null; bei leerem Result, sondern return Set.of();
2. Consider static factory methods instead of constructors
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
Nicht immer besser, können aber in manchen Anwendungsfällen zahlreiche Vorteile bieten:
- One advantage of static factory methods is that, unlike constructors, they have names.
- They are not required to create a new object each time they’re invoked.
- They can return an object of any subtype of their return type.
- The class of the returned object can vary from call to call as a function of the input parameters.
- The class of the returned object need not exist when the class containing the method is written.
Nachteile:
- The main limitation of providing only static factory methods is that classes without public or protected constructors cannot be subclassed.
- A second shortcoming of static factory methods is that they are hard for programmers to find.
3. Consider a builder when faced with many constructor parameters
Static factories and constructors share a limitation: they do not scale well to large numbers of optional parameters. This one hits some projects I worked on very hard.
4. Enforce the singleton property with a private constructor or an enum type
Alles was nicht mehrere Instanzen braucht, soll nur einmal instanziiert werden. Sei es mit @ApplicationScoped oder einem anderen Singleton Pattern.
5. Enforce noninstantiability with a private constructor
Occasionally you’ll want to write a class that is just a grouping of static methods and static fields. Such classes have acquired a bad reputation because some people abuse them to avoid thinking in terms of objects, but they do have valid uses.
private MyStaticClass() {
// private constructor to prevent instantiation
}
6. Avoid creating unnecessary objects
String s = new String("bikini"); // DON'T DO THIS!
oder:
// Hideously slow! Can you spot the object creation?
private static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++)
sum += i;
return sum;
}
Auflösung: This program gets the right answer, but it is much slower than it should be, due to a one-character typographical error. The variable sum is declared as a Long instead of a long, which means that the program constructs about 2^31 unnecessary Long instances (roughly one for each time the long i is added to the Long sum).
7. Avoid finalizers and cleaners
Finalizers are unpredictable, often dangerous, and generally unnecessary. Their use can cause erratic behavior, poor performance, and portability problems.
8. Prefer try-with-resources to try-finally
// try-with-resources - the best way to close resources!
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
9. Consider implementing Comparable
By implementing Comparable, you allow your class to interoperate with all of the many generic algorithms and collection implementations that depend on this interface.
10. Minimize the accessibility of classes and members
The single most important factor that distinguishes a well-designed component from a poorly designed one is the degree to which the component hides its internal data and other implementation details from other components. A well-designed component hides all its implementation details, cleanly separating its API from its implementation. Components then communicate only through their APIs and are oblivious to each other’s inner workings. This concept, known as information hiding or encapsulation, is a fundamental tenet of software design.
11. In public classes, use accessor methods, not public fields
Occasionally, you may be tempted to write degenerate classes that serve no purpose other than to group instance fields:
// Degenerate classes like this should not be public!
class Point {
public double x;
public double y;
}
Because the data fields of such classes are accessed directly, these classes do not offer the benefits of encapsulation. You can’t change the representation without changing the API, you can’t enforce invariants, and you can’t take auxiliary action when a field is accessed. Hard-line object-oriented programmers feel that such classes are anathema and should always be replaced by classes with private fields and public accessor methods (getters) and, for mutable classes, mutators (setters):
// Encapsulation of data by accessor methods and mutators
class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() { return x; }
public double getY() { return y; }
public void setX(double x) { this.x = x; }
public void setY(double y) { this.y = y; }
}
Certainly, the hardliners are correct when it comes to public classes: if a class is accessible outside its package, provide accessor methods to preserve the flexibility to change the class’s internal representation. If a public class exposes its data fields, all hope of changing its representation is lost because client code can be distributed far and wide.
However, if a class is package-private or is a private nested class, there is nothing inherently wrong with exposing its data fields—assuming they do an adequate job of describing the abstraction provided by the class.
12. Minimize mutability
An immutable class is simply a class whose instances cannot be modified. All the information contained in each instance is fixed for the lifetime of the object, so no changes can ever be observed. The Java platform libraries contain many immutable classes, including String, the boxed primitive classes, and BigInteger and BigDecimal. There are many good reasons for this: Immutable classes are easier to design, implement, and use than mutable classes. They are less prone to error and are more secure. To make a class immutable, follow these five rules:
- Don’t provide methods that modify the object’s state (known as mutators).
-
Ensure that the class can’t be extended. This prevents careless or malicious subclasses from compromising the immutable behavior of the class by behaving as if the object’s
state has changed. Preventing subclassing is generally accomplished by making the class
final, for instance. - Make all fields final. This clearly expresses your intent in a manner that is enforced by the system. Also, it is necessary to ensure correct behavior if a reference to a newly created instance is passed from one thread to another without synchronization, as spelled out in the memory model.
- Make all fields private. This prevents clients from obtaining access to mutable objects referred to by fields and modifying these objects directly. While it is technically permissible for immutable classes to have public final fields containing primitive values or references to immutable objects, it is not recommended because it precludes changing the internal representation in a later release.
-
Ensure exclusive access to any mutable components. If your class has any fields that refer to mutable objects, ensure that clients of the class cannot obtain references to
these objects. Never initialize such a field to a client-provided object reference or return the field from an accessor. Make defensive copies in constructors, accessors, and
readObjectmethods.
13. Favor composition over inheritance
Inheritance is a powerful way to achieve code reuse, but it is not always the best tool for the job. Used inappropriately, it leads to fragile software. It is safe to use inheritance within a package, where the subclass and the superclass implementations are under the control of the same programmers. It is also safe to use inheritance when extending classes specifically designed and documented for extension. Inheriting from ordinary concrete classes across package boundaries, however, is dangerous.
Unlike method invocation, inheritance violates encapsulation. You might think that it is safe to extend a class if you merely add new methods and refrain from overriding existing methods. While this sort of extension is much safer, it is not without risk. If the superclass acquires a new method in a subsequent release and you have the bad luck to have given the subclass a method with the same signature and a different return type, your subclass will no longer compile. If you’ve given the subclass a method with the same signature and return type as the new superclass method, then you’re now overriding it, so you’re subject to the problems described earlier. Furthermore, it is doubtful that your method will fulfill the contract of the new superclass method, because that contract had not yet been written when you wrote the subclass method.
Luckily, there is a way to avoid all the problems described above. Instead of extending an existing class, give your new class a private field that references an instance of the existing class. This design is called composition because the existing class becomes a component of the new one. Each instance method in the new class invokes the corresponding method on the contained instance of the existing class and returns the results. This is known as forwarding, and the methods in the new class are known as forwarding methods. The resulting class will be rock solid, with no dependencies on the implementation details of the existing class. Even adding new methods to the existing class will have no impact on the new class.
14. Prefer interfaces to abstract classes
Java has two mechanisms to define a type that permits multiple implementations: interfaces and abstract classes. Since the introduction of default methods for interfaces in Java 8, both mechanisms allow you to provide implementations for some instance methods.
A major difference is that to implement the type defined by an abstract class, a class must be a subclass of the abstract class. Because Java permits only single inheritance, this restriction on abstract classes severely constrains their use as type definitions. Any class that defines all the required methods and obeys the general contract is permitted to implement an interface, regardless of where the class resides in the class hierarchy. Existing classes can easily be retrofitted to implement a new interface.
- Interfaces are ideal for defining mixins.
- Interfaces allow for the construction of nonhierarchical type frameworks.
- Interfaces enable safe, powerful functionality enhancements via the wrapper class idiom. If you use abstract classes to define types, you leave the programmer who wants to add functionality with no alternative but inheritance.
15. Generics: Don't use raw types
You don’t discover the error until runtime, long after it has happened, and in code that may be distant from the code containing the error. Once you see the ClassCastException, you have to search through the codebase looking for the method invocation that put the coin into the stamp collection. The compiler can’t help you.
16. Generics: Eliminate unchecked warnings
When you program with generics, you will see many compiler warnings:
- unchecked cast warnings
- unchecked method invocation warnings
- unchecked parameterized vararg type warnings
- unchecked conversion warnings.
The more experience you acquire with generics, the fewer warnings you’ll get, but don’t expect newly written code to compile cleanly. Many unchecked warnings are easy to eliminate. For example, suppose you accidentally write this declaration:
Set<Lark> exaltation = new HashSet();
Change this to:
Set<Lark> exaltation = new HashSet<>();
Some warnings will be much more difficult to eliminate. When you get warnings that require some thought, persevere! Eliminate every unchecked warning that you can.
If you eliminate all warnings, you are assured that your code is typesafe, which is a very good thing. It means that you won’t get a ClassCastException at runtime, and it increases your confidence
that your program will behave as you intended. If you can’t eliminate a warning, but you can prove that the code that provoked the warning is typesafe, then (and only then) suppress the
warning with an @SuppressWarnings("unchecked") annotation. If you suppress warnings without first proving that the code is typesafe, you are giving yourself a false sense
of security.
The code may compile without emitting any warnings, but it can still throw a ClassCastException at runtime.
Every time you use a @SuppressWarnings("unchecked") annotation, add a comment saying why it is safe to do so.
17. Generics: When it makes sense
Ouch - a class badly in need of generics:
So etwas sollte spätestens beim Merge Request abgefangen werden!
18. Generics: How not to do it
Sorry, no words needed. Please NEVER merge this:
public abstract class JpaLearningCardBaseContentService<LC extends LearningCardBaseContent<L, LC, AC>, J extends JpaLearningCardBaseContent<L, LC, AC>, L extends
LearningCardBase<LC, A>, A extends AnswerOptionBase<L, AC>, AC extends AnswerOptionBaseContent<LC, AC, A>, AS extends AnswerOptionBaseService<A, AC, L, LC>, ACS extends
AnswerOptionBaseContentService<AC, A, L, LC>, LS extends LearningCardBaseService<L>>
extends JpaLightPersistenceService<LC, J, Long> implements LearningCardBaseContentService<LC, L, A, AC> {
// ...
}
A mere class declaration should never take 5 minutes to understand.
19. Prefer lists to arrays
Arrays differ from generic types in two important ways. First, arrays are covariant. This scary-sounding word means simply that if Sub is a subtype of Super, then the
array type Sub[] is a subtype of the array type Super[]. Generics, by contrast, are invariant: for any two distinct types Type1 and Type2,
List<Type1> is neither a subtype nor a supertype of List<Type2>.
20. Prefer lambdas to anonymous classes
// Anonymous class instance as a function object - obsolete!
Collections.sort(words, new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
});
Instead, do this:
// Lambda expression as function object (replaces anonymous class)
Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));
21. Minimize the scope of local variables
The most powerful technique for minimizing the scope of a local variable is to declare it where it is first used...
22. Prefer for-each loops to traditional for loops
// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
... // Do something with e
}
23. Know and use the libraries
Suppose you want to generate random integers between zero and some upper bound. Faced with this common task, many programmers would write a little method that looks something like this:
// Common but deeply flawed!
static Random rnd = new Random();
static int random(int n) {
return Math.abs(rnd.nextInt()) % n;
}
This method may look good, but it has three flaws. The first is that if n is a small power of two, the sequence of random numbers will repeat itself after a fairly short period. The second flaw is that if n is not a power of two, some numbers will, on average, be returned more frequently than others. If n is large, this effect can be quite pronounced.
The third flaw in the random method is that it can, on rare occasions, fail catastrophically, returning a number outside the specified range. This is so because the
method attempts to map the value returned by rnd.nextInt() to a non-negative int by calling Math.abs. If nextInt() returns
Integer.MIN_VALUE, then Math.abs will also return Integer.MIN_VALUE, and the remainder operator (%) will return a negative number, assuming
nis not a power of two. This will almost certainly cause your program to fail, and the failure may be difficult to reproduce.
24. Avoid float and double if exact answers are required
BigDecimal für Geldbeträge verwenden. float und double erzeugen Rundungsfehler.
For example, suppose you have $1.03 in your pocket, and you spend 42¢. How much money do you have left? Here’s a naive program fragment that attempts to answer this question:
System.out.println(1.03 - 0.42);
Unfortunately, it prints out 0.6100000000000001. This is not an isolated case. Suppose you have a dollar in your pocket, and you buy nine washers priced at ten cents each. How much
change do you get?
System.out.println(1.00 - 9 * 0.10);
According to this program fragment, you get $0.09999999999999998.
You might think that the problem could be solved merely by rounding results prior to printing, but unfortunately, this does not always work. For example, suppose you have a dollar in your pocket, and you see a shelf with a row of delicious candies priced at 10¢, 20¢, 30¢, and so forth, up to a dollar. You buy one of each candy, starting with the one that costs 10¢, until you can’t afford to buy the next candy on the shelf.
25. Prefer primitive types to boxed primitives
// Broken comparator - can you spot the flaw?
Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);
26. Refer to objects by their interfaces
Good example:
Set<Son> sonSet = new LinkedHashSet<>();
Bad example:
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();
27. Prefer interfaces to reflection
If not:
- You lose all the benefits of compile-time type checking, including exception checking. If a program attempts to invoke a nonexistent or inaccessible method reflectively, it will fail at runtime unless you’ve taken special precautions.
- The code required to perform reflective access is clumsy and verbose. It is tedious to write and difficult to read.
- Performance suffers. Reflective method invocation is much slower than normal method invocation.
28. Adhere to generally accepted naming conventions
The Java platform has a well-established set of naming conventions, many of which are contained in The Java Language Specification [JLS, 6.1].
29. Use exceptions only for exceptional conditions
// Horrible abuse of exceptions. Don't ever do this!
try {
int i = 0;
while (true) {
range[i++].climb();
}
} catch (ArrayIndexOutOfBoundsException e) {
// catch it
}
What does this code do? It’s not at all obvious from inspection, and that’s reason enough not to use it . It turns out to be a horribly ill-conceived idiom for looping through the elements of an
array. The infinite loop terminates by throwing, catching, and ignoring an ArrayIndexOutOfBoundsException when it
attempts to access the first array element outside the bounds of the array. It’s supposed to be equivalent to the standard idiom for looping through an array, which is instantly recognizable to
any Java programmer:
for (Mountain m : range) {
m.climb();
}
Exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow.
30. Use checked exceptions for recoverable conditions and runtime exceptions for programming errors
Use checked exceptions for conditions from which the caller can reasonably be expected to recover. By confronting the user with a checked exception, the API designer presents a mandate to recover from the condition. The user can disregard the mandate by catching the exception and ignoring it, but this is usually a bad idea.
Use runtime exceptions to indicate programming errors. The great majority of runtime exceptions indicate precondition violations. A precondition violation is simply a failure by
the client of an API to adhere to the contract established by the API specification. For example, the contract for array access specifies that the array index must be between zero and the array
length minus one, inclusive. ArrayIndexOutOfBoundsException indicates that this precondition was violated.
31. Avoid unnecessary use of checked exceptions
In summary, when used sparingly, checked exceptions can increase the reliability of programs; when overused, they make APIs painful to use.
Pair<Boolean, LearningCardGroup> answerLearningCard(@NotNull LearningCardGroup learningCardGroup,
LearningCardAssignment learningCardAssignment,
@NotNull Collection<AnswerOption> answers)
throws InvalidTrainingStateException, PersistenceException, LearningCardGroupAlreadyCompletedException,
ConsecutiveCorrectCountExceededException,
PreviousLearningCardGroupUncompletedException, LearningCardGroupCooldownException,
LearningCardGroupHasUncompletedLearningCardsException, InvalidParticipantException, TrainingNotSignedException; // but why ???
// this is a mistake seen a lot with junior devs
// exceptions are exceptionally slow and should be avoided for mere validation
// the performance gets even worse if a stacktrace is printed, since logging and printing is one of the slowest possible actions
32. Favor the use of standard exceptions
An attribute that distinguishes expert programmers from less experienced ones is that experts strive for and usually achieve a high degree of code reuse.
Exceptions are no exception to the rule that code reuse is a good thing. The Java libraries provide a set of exceptions that covers most of the exception-throwing needs of most APIs. Reusing standard exceptions has several benefits. Chief among them is that it makes your API easier to learn and use because it matches the established conventions that programmers are already familiar with. A close second is that programs using your API are easier to read because they aren’t cluttered with unfamiliar exceptions. Last (and least), fewer exception classes means a smaller memory footprint and less time spent loading classes.
The most commonly reused exception type is IllegalArgumentException. This is generally the exception to throw when the caller passes in an argument whose value is inappropriate. For
example, this would be the exception to throw if the caller passed a negative number in a parameter representing the number of times some action was to be repeated. Another commonly reused
exception is IllegalStateException. This is generally the exception to throw if the invocation is illegal because of the state of the receiving object. For example, this would be the
exception to throw if the caller attempted to use some object before it had been properly initialized.
Arguably, every erroneous method invocation boils down to an illegal argument or state, but other exceptions are standardly used for certain kinds of illegal arguments and states. If a caller
passes null in some parameter for which null values are prohibited, convention dictates that NullPointerException be thrown rather than IllegalArgumentException.
Similarly, if a caller passes an out-of-range value in a parameter representing an index into a sequence, IndexOutOfBoundsException should be thrown rather than IllegalArgumentException.
Do not reuse Exception, RuntimeException, Throwable, or Error directly. Treat these classes as if they were abstract. Though RuntimeException may be used if you want
to convert a checked exception into a runtime exception, and you really know what you are doing.
33. Don’t ignore exceptions
// Empty catch block ignores exception - Highly suspect!
try {
...
} catch (SomeException e) {
}
An empty catch block defeats the purpose of exceptions, which is to force you to handle exceptional conditions. Ignoring an exception is analogous to ignoring a fire alarm – and turning it off so no one else gets a chance to see if there’s a real fire.