QR code

remove(42) vs. find(42).remove()

  • Moscow, Russia
  • comments

OOPoop

We have a list of books in the books object. How do we remove a single book from it, given that we know its ID? We can do books.removeById(42). Alternatively, we can find it with books.findById(42) and then call b.remove(). Which option should we prefer, and why? The second choice is the better one. Not only because it’s more object-oriented, but also due to several practical advantages.

8 Femmes (2002) by François Ozon
8 Femmes (2002) by François Ozon

Extending the Deletion Behavior

What if we want to extend the deletion algorithm? For example, suppose we want to print a log message every time a book is deleted.

If we follow the open-closed principle, we should avoid modifying the existing Book or Books classes. Instead, we want to either extend or decorate them. A decorator is the ideal choice if we want to avoid implementation inheritance.

If we use removeById(), we must decorate the books object. If we use remove(), we can decorate the individual book retrieved via findById(). The latter approach may be more cohesive, since its decoratee is likely smaller and more focused:

b = books.findById(42)
b = Logged.new(b)
b.remove!

Handling Missing or Non-Deletable Books

What if the book isn’t found, or can’t be deleted? The removeById() approach doesn’t support the Null Object pattern well. It forces us to throw an exception—or worse, return false, which goes against the CQRS principle.

On the other hand, returning an object, even a null or fake object, offers more flexible error handling. For instance, we can access title() safely, knowing that the book was found successfully—or catch and re-raise exceptions with more context:

b = books.findById(42)
begin
  b.remove!
rescue e
  raise e, "Can't delete book #{b.title}"
end

Decoupling Retrieval and Deletion

What if the retrieval and deletion of the object happen far apart in the code?

If books knows how to find, and a book knows how to delete, we keep responsibilities nicely separated. It also allows the book to be immutable and encapsulate its own ID, reducing leakage of internal data:

b = books.findById(42)
# +100 lines here...
b.remove!  # DELETE FROM book WHERE id = 42
# +200 lines here...
if b.exists?  # SELECT * FROM book WHERE id = 42
  puts "The book is back to the library!"
end

Once the ID is wrapped inside the book by findById(), it never has to leak out again. This reduces the amount of naked data exposed to parts of the code that don’t need it.

The less data is visible, the better the design—at least from an OOP perspective.

Thus, using findById() followed by remove() embraces object-oriented principles more fully. It allows for cleaner extension, better error handling, and tighter encapsulation.

sixnines availability badge  GitHub stars