Notes on the book "Elegant Objects" by Yegor Bugaenko

Notes on the book Elegant Objects by Yegor Bugaenko

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.

Class should be named based on what it is, not what it does.

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.

Don't Create Objects That End With -ER
Manager. Controller. Helper. Handler. Writer. Reader. Converter. Validator. Router. Dispatcher. Observer. Listener. Sorter. Encoder. Decoder. This is the class names hall of shame. Have you seen them in your code? In open source libraries you're using? In pattern books? They are all wrong. What do they have in common?

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.

There Can Be Only One Primary Constructor
I suggest classifying class constructors in OOP as primary and secondary. A primary constructor is the one that constructs an object and encapsulates other objects inside it. A secondary one is simply a preparation step before calling a primary constructor and is not really a constructor but rather an introductory layer in front of a real constructing mechanism.

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.
Constructors Must Be Code-Free
How much work should be done within a constructor? It seems reasonable to do some computations inside a constructor and then encapsulate results. That way, when the results are required by object methods, we'll have them ready. Sounds like a good approach? No, it's not.

Encapsulate as few fields as possible in classes

A class with few fields is easier to maintain and test.

No more than four fields should be encapsulated.

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.

How Much Your Objects Encapsulate?
Which line do you like more, the first or the second: What is the difference? The first class HTTP encapsulates a URL, while the second one expects it as an argument of method read(). Technically, both objects do exactly the same thing: they read the content of the Google home page.

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.
Seven Virtues of a Good Object
Martin Fowler says: A library is essentially a set of functions that you can call, these days usually organized into classes. Functions organized into classes? With all due respect, this is wrong. And it is a very common misconception of a class in object-oriented programming. Classes are not organizers of functions.

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.


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.


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.


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.

Public Static Literals ... Are Not a Solution for Data Duplication
I have a new String(array,"UTF-8") in one place and exactly the same code in another place in my app. Actually, I may have it in many places. And every time, I have to use that "UTF-8" constant in order to create a String from a byte array.

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.


  • 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.
Objects Should Be Immutable
In object-oriented programming, an object is immutable if its state can't be modified after it is created. In Java, a good example of an immutable object is . Once created, we can't modify its state. We can request that it creates new strings, but its own state will never change.

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.

The perfect code speaks for itself and requires no additional documentation.

Poorly designed classes require you to write documentation for them. Therefore, if the class is well designed, documentation is not required.

Don't document the code - make it cleaner.

It's better to write simple understandable classes than to write documentation for them.

Well-written unit tests are useful documentation of class usage.

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.

Built-in Fake Objects
While mock objects are perfect instruments for unit testing, mocking through mock frameworks may turn your unit tests into an unmaintainable mess. Thanks to them we often hear that "mocking is bad" and "mocking is evil." The root cause of this complexity is that our objects are too big.

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.

OOP Alternative to Utility Classes
A utility class (aka helper class) is a "structure" that has only static methods and encapsulates no state. StringUtils, IOUtils, FileUtils from Apache Commons; Iterables and Iterators from Guava, and from JDK7 are perfect examples of utility classes. This design idea is very popular in the Java world (as well as C#, Ruby, etc.)

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.

Why NULL is Bad?
A simple example of NULL usage in Java: What is wrong with this method? It may return NULL instead of an object-that's what is wrong. NULL is a terrible practice in an object-oriented paradigm and should be avoided at all costs.

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.

Getters/Setters. Evil. Period.
There is an old debate, started in 2003 by Allen Holub in this Why getter and setter methods are evil famous article, about whether getters/setters is an anti-pattern and should be avoided or if it is something we inevitably need in object-oriented programming. I'll try to add my two cents to this discussion.

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.

Operator new() is Toxic
To instantiate objects, in most object-oriented languages, including Java, Ruby, and C++, we use operator new(). Well, unless we use static factory methods, which we don't use because they are evil. Even though it looks so easy to make a new object any time we need it, I would recommend to be more careful with this rather toxic operator.

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.

Class Casting Is a Discriminating Anti-Pattern
Type casting is a very useful technique when there is no time or desire to think and design objects properly. Type casting (or class casting) helps us work with provided objects differently, based on the class they belong to or the interface they implement.

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.
Why NULL is Bad?
A simple example of NULL usage in Java: What is wrong with this method? It may return NULL instead of an object-that's what is wrong. NULL is a terrible practice in an object-oriented paradigm and should be avoided at all costs.

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.
Checked vs. Unchecked Exceptions: The Debate Is Not Over
Do we need checked exceptions at all? The debate is over, isn't it? Not for me. While most object-oriented languages don't have them, and most programmers think checked exceptions are a Java mistake, I believe in the opposite-unchecked exceptions are the mistake. Moreover, I believe multiple exception types are a bad idea too.

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.
Seven Virtues of a Good Object
Martin Fowler says: A library is essentially a set of functions that you can call, these days usually organized into classes. Functions organized into classes? With all due respect, this is wrong. And it is a very common misconception of a class in object-oriented programming. Classes are not organizers of functions.


В книге много полезных и практичных советов с примерами. С некоторыми из них можно не соглашаться или их будет трудно применить в уже существующих проектах. Но постепенное их принятие и применение приведет к улучшению ваших навыков и сопровождаемости ваших проектов, в которых используется ООП.

Elegant Objects book website:

Elegant Objects
Elegant Objects (EO) is an object-oriented programming paradigm that renounces traditional techniques like null, getters-and-setters, code in constructors, mutable objects, static methods, annotations, type casting, implementation inheritance, data objects, etc. Books: Online: Telegram chat and@painofoop (Russian only). Join our soalition if you are ready to help. Twitter: #elegantobjects.

Website of the author of the book Yegor Bugaenko:

Elegant Objects
"Elegant Objects" is my first book. It is about object-oriented programming from an idealistic and, maybe, a bit of a futuristic point of view. There are 23 independent recommendations for a practical OOP designer on how to make code cleaner, classes more solid, and architecture more visible.

Since you've made it this far, sharing this article would be highly appreciated!