In this article I want to share my notes on Egor Bugaenko's book "Elegant Objects. Java Edition". A book about Object-Oriented Programming (OOP) with practical tips on how to improve the maintenance and design of projects. The tips are aimed at making classes cleaner, more seamless, and easier to understand. For most of the tips I have provided a link to Egor Bugaenko's website, where you can explore the advice in more detail with examples and comments.
Tips and principles for use
Don't use the -er ending in the class name
A class name with "-er" indicates that it is a set of procedures for manipulating data.
A complete object is a representative of its own data, not a mindless transition between its internal state and the external world.
Examples of bad names:
Manager, Controller, Helper, Handler, Writer, Reader, Converter, Validator (-or is also harmful), Router, Dispatcher, Observer, Listener, Sorter, Encoder and Decoder.
The exceptions are User, Computer.
Make one main constructor in the class
The constructor is the point of entry and creation of the new object.
There should only be one main constructor that initializes properties. Other constructors with a different data set must use the main constructor.
This improves maintainability of class objects.
Make constructors without executing code
Constructors should only create an object without doing any processing of the data passed to them.
Handling data will be done on demand by calling methods of the object.
The upside of this approach:
- Simplifies constructors: they only initialize fields.
- Data conversion work only happens when it's really needed. The result of conversions can be cached in the decorator, if needed.
Encapsulate as few fields as possible in classes
A class with few fields is easier to maintain and test.
This makes it easy to identify the object in the system. The identity of the object is like the coordinates of the object in the system. The more coordinates, the more complicated the maintainability becomes.
Encapsulate something in the class
A complete class in OOP cannot be a set of methods without properties. It coexists with other classes in the system and uses them. Therefore, a class encapsulates other classes to do its job.
Having a state in a class object also helps identify the object in the system.
Always use interfaces
The benefits of using interfaces:
- They describe the class contract: what the class can do. Improves organization and maintainability.
- Allow you to decouple classes from each other: it's easier to swap classes and test.
Name the methods thoughtfully
- Name "Builder" methods that return something in the response with nouns.
You don't need to add prefixes in the method name: get, create, fetch. The method itself needs to know how to create a result. Adding a prefix makes the method call look like an order.
Examples:
int pow(int base, int power);
int speed ();
Employee employee(int id);
String parsedCell(int Ρ
, int Ρ);
- The Manipulators methods are called verbs. They do not return anything in response.
A verb indicates that a method does some work and modifications. There is no result in the response of such a method, because under certain conditions, the work may not be done.
Examples:
void save(String content);
void put(String key, Float value);
void remove(Employee emp);
void quicklyPrint(int id);
- Name methods that return a boolean type result as adjectives.
Examples:
boolean empty ();
boolean readable ();
boolean negative();
Do not use public constants
Constants are used to share data.Ρ
The negative consequences of using public constants by objects of different classes:
- Introducing entanglement: classes become hardwired. This has a negative effect on maintainability and testing.
- Loss of wholeness: classes become dependent on external logic, the class becomes less self-sufficient and encapsulated. And also public constants can be used in many different domains. The constant does not control where it is used.
Instead of public constants, you should create new classes that help classes share constant logic.
Make classes immutable
By using immutable classes, we improve maintainability. Immutable classes are easier to understand and maintain.
With objects of these immutable classes, you can't change state after you create them. If we want to change the state of an object, we should create a new object with the modified state and return it as the result of a method call.
Pros:
- Atomicity of failures: we either have a consistent object or a failure occurs. There is no intermediate state.
- No temporal entanglement: having the ability to change an object's state with setters can lead to run-time errors in the application. This can be in the wrong order of using setters, or missing calls.
- No side-effects: No unobvious object state changes when methods are invoked.
- No NULL links: objects are created in a consistent state. There is no need to check for NULL at further object's work.
- Thread-safe: immutable objects are always in predictable state and can be used in parallel in different threads
- Smaller and simpler objects: immutability helps make classes cleaner and shorter. It's hard to make an immutable class too big.
Write tests instead of documentation
Supporting information is very important for maintainability. It helps the reader better understand the use of the code.
If there is a need to write documentation, this can be a sign of poorly designed code.
Poorly designed classes require you to write documentation for them. Therefore, if the class is well designed, documentation is not required.
It's better to write simple understandable classes than to write documentation for them.
In a unit test, you can view:
- Class usage variants.
- The input data.
- Errors that may be raised while the class is running.
Use fake objects instead of mock objects
Mocking makes test maintenance difficult because it turns assumptions into facts. Mocks reveal the internal logic of a class. Classes cease to be black boxes in testing.
Fake classes improve test maintainability. They help to better think and design the class interface.
Make a maximum of 5 public methods in a class
Pros of small classes:
- Less likely to make a mistake: it's easier to reconcile a small number of methods.
- Better maintainability: less code, fewer methods, easier to understand and maintain.
- It's easier to achieve class wholeness - linking class methods to all of its properties.
- Easier to test: fewer entry points and easier to reproduce all use cases.
Do not use static methods
Static methods are more of a procedural programming style than OOP. They complicate maintainability and testing.
Also, you should not use utility classes. This is not a complete object, but a set of procedures.
Do not allow arguments with NULL values
By allowing NULL
as an argument you will need to add a check for NULL
in the method. In order to handle this kind of method call. This complicates the logic and reduces maintainability.
If the method user did pass NULL
, it is best to ignore such use and not do additional validation. Eventually this will still result in a NullPointerException
and the user will need to correct the error.
Do not use getters and setters
Complete objects in OOP are not simple data structures.
The data structure is a data set, a transparent box. We communicate directly with its data. The data structure is passive.
An object is a black box with its own operating logic and encapsulated data.
Hence, getters and setters in objects break its integrity and reveal its details. In this case, encapsulation is not is used to its full potential. Such objects become like data structures.
Do not use the new operator outside of secondary constructors
You should not create an object of another class in a class. This ties the two classes together in a hard way. This creates difficulties in maintaining and testing. It is impossible to substitute the class of the object being created without changing the code.
It is better to use dependency injection using interfaces.
In the secondary constructors, you can use new()
to create objects of your class.
Avoid introspection and casting types
These are the instanceof
operator and Class.cast()
method in Java or their equivalents in other languages.
Type casting and type checking have a negative effect on code understanding and maintainability.
This is the discrimination of objects by type. Depending on the type of object, we use it in different ways.
Using introspection and type casting we express our expectations about the object without documenting them. This becomes opaque hidden relationships that affect maintainability.
Never return NULL
The return of NULL
introduces distrust to the class. It forces the users of the class to double-check and process the result of its work.
The return of NULL
is an attempt of a safe failure, which leads to a poor maintainability.
There are three solutions:
- Throw an exception for fail fast. This helps the client to see and handle the error faster.
- Return a collection of objects. This helps to handle a case with an empty object search result.
- Use the "Null object" pattern if it fits the case. Such an object looks like a real object, but it behaves differently.
Throw only checked exceptions
Checked exceptions in Java add contract and improve code documentation. It is better to use them instead of unchecked ones.
A few more tips:
- Don't catch exceptions unnecessarily: exception handling is better done at higher levels of the application
- Build chains of exceptions: catch an exception at the lowest level and throw a new one with additional information and the caught exception in the chain. This improves error context.
- Restore once: restoring and returning the error to the user should be at the top level of the application.
Make classes either constant (final) or abstract
With common class inheritance, maintainability problems can arise, because the relationships between classes can become confusing. Methods can be overridden at different levels of inheritance. This increases the complexity and It becomes harder to read and understand the code.
It is better to use constant (final) classes or abstract parent classes. This applies both to the class itself, and methods.
This approach makes the intentions clear and prevents the possibility of inheritance failure:
- final is a black box for its users. It can only be encapsulated.
- abstract is an unfinished transparent box, a half-finished product. It has missing components that need to be made.
Summary
Π ΠΊΠ½ΠΈΠ³Π΅ ΠΌΠ½ΠΎΠ³ΠΎ ΠΏΠΎΠ»Π΅Π·Π½ΡΡ ΠΈ ΠΏΡΠ°ΠΊΡΠΈΡΠ½ΡΡ ΡΠΎΠ²Π΅ΡΠΎΠ² Ρ ΠΏΡΠΈΠΌΠ΅ΡΠ°ΠΌΠΈ. Π‘ Π½Π΅ΠΊΠΎΡΠΎΡΡΠΌΠΈ ΠΈΠ· Π½ΠΈΡ ΠΌΠΎΠΆΠ½ΠΎ Π½Π΅ ΡΠΎΠ³Π»Π°ΡΠ°ΡΡΡΡ ΠΈΠ»ΠΈ ΠΈΡ Π±ΡΠ΄Π΅Ρ ΡΡΡΠ΄Π½ΠΎ ΠΏΡΠΈΠΌΠ΅Π½ΠΈΡΡ Π² ΡΠΆΠ΅ ΡΡΡΠ΅ΡΡΠ²ΡΡΡΠΈΡ ΠΏΡΠΎΠ΅ΠΊΡΠ°Ρ . ΠΠΎ ΠΏΠΎΡΡΠ΅ΠΏΠ΅Π½Π½ΠΎΠ΅ ΠΈΡ ΠΏΡΠΈΠ½ΡΡΠΈΠ΅ ΠΈ ΠΏΡΠΈΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ ΠΏΡΠΈΠ²Π΅Π΄Π΅Ρ ΠΊ ΡΠ»ΡΡΡΠ΅Π½ΠΈΡ Π²Π°ΡΠΈΡ Π½Π°Π²ΡΠΊΠΎΠ² ΠΈ ΡΠΎΠΏΡΠΎΠ²ΠΎΠΆΠ΄Π°Π΅ΠΌΠΎΡΡΠΈ Π²Π°ΡΠΈΡ ΠΏΡΠΎΠ΅ΠΊΡΠΎΠ², Π² ΠΊΠΎΡΠΎΡΡΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ ΠΠΠ.
Useful Links
Elegant Objects book website:
Website of the author of the book Yegor Bugaenko:
Since you've made it this far, sharing this article would be highly appreciated!
Published