In lesson 2.14.1, a test fake was on the list of smells: a class that implements a store interface and fills most of the methods with throw new NotImplementedException() because the test doesn’t need them. That fake isn’t a mistake in the test. It shows that the interface is too wide for its callers. The I in SOLID, the interface segregation principle, says: no caller should depend on methods it doesn’t use. In this lesson you cut the Kladde’s store so that every caller gets exactly what it needs.

The smell: an interface that grew along

Since lesson 1.4.4, the Kladde has its contract IKladdeStore. In lesson 2.4.11 it was small: load everything, save everything. With the database came the wish to stop loading everything, and in many Kladdes the contract has grown since, one method for every new query. It looks something like this:

C#
/// <summary>Before: everything the Kladde ever needed from its store, in one contract.</summary>
public interface IKladdeStore
{
    Task<IReadOnlyList<Contact>> SearchContactsAsync(string term, int page, CancellationToken cancellationToken);
    Task<Contact?> FindContactAsync(int id, CancellationToken cancellationToken);
    Task<Result<Contact>> AddContactAsync(string name, string email, CancellationToken cancellationToken);
    Task UpdateContactAsync(Contact contact, CancellationToken cancellationToken);
    Task DeleteContactAsync(int id, CancellationToken cancellationToken);
    Task<IReadOnlyList<Appointment>> AppointmentsBetweenAsync(DateTimeOffset from, DateTimeOffset to, CancellationToken cancellationToken);
    Task<Result<Appointment>> AddAppointmentAsync(string title, TimeRange time, CancellationToken cancellationToken);
    Task DeleteAppointmentAsync(int id, CancellationToken cancellationToken);
    Task<IReadOnlyList<Booking>> BookingsBetweenAsync(DateTimeOffset from, DateTimeOffset to, CancellationToken cancellationToken);
    Task<Result<Booking>> AddBookingAsync(string name, string email, TimeRange time, CancellationToken cancellationToken);
    Task ConfirmBookingAsync(Guid id, CancellationToken cancellationToken);
    Task DeleteBookingAsync(Guid id, CancellationToken cancellationToken);
}

Twelve methods, and in your Kladde there may be more. If your IKladdeStore is still the one from lesson 2.4.11 with LoadAsync and SaveAsync, the same width sits somewhere else: the free times page loads the whole Kladde with all contacts and notes to see one day’s appointments, and every fake has to build a complete KladdeContents. This lesson’s cut is the same.

Who needs what? The free times page (lesson 2.5.6) needs one thing: the taken times of one day, from appointments and bookings. It gets twelve methods, including deleting contacts. Three pains follow from that:

  • Every fake builds everything. A test of the free times needs a fake with twelve methods, eleven of them with NotImplementedException. If a thirteenth method is added, every fake in every test project breaks, even where it has nothing to do with the new method.
  • Every change hits everyone. If the signature of SearchContactsAsync changes, every class that knows IKladdeStore has to be recompiled, the free times page included.
  • The page could do more than it may. A public page that could delete contacts is an unnecessary danger (lesson 2.6.6). What a part doesn’t get, it can’t get wrong.