Потерянная абстракция

Давайте с места в карьер. Представим, что есть программа, которая управляет выдачей книг читателям. За каждым читателем закрепляется полка с книгами, которые ему доступны.

У нас будет класс Reader, который представляет читателя. Он принимает данные о пользователе и полке, привязанной к нему.

Ещё у нас будет код, который использует экземпляр этого класса в условии внизу. В нём мы будем проверять, есть ли определённая книга на полке у этого читателя и может ли он её прочесть.

Вопрос: что не так с этим условием в конце?

class Reader {
	constructor(user, shelf) {
		this.user = user;
		this.shelf = shelf;
	}

	getBooksFromShelf = () => this.shelf.getBooks();
}

const reader = new Reader(someUserData, someBookShelfData);
const books = reader.getBooksFromShelf();

// вот тут проблема ↓
if (books.contains(book.id)) {
	// ...
}

На первый взгляд всё, вроде, адекватно, но можно сделать лучше.

Смысл вместо реализации

Сейчас код показывает, как действие реализуется технически, в понятиях структур данных. Техническая реализация обычно многословна и сложна, поэтому понять сразу, что тут происходит, — трудно.

Но как только мы…

// заменим условие на метод-предикат canRead,
// в который вынесем всю техническую реализацию...
if (reader.canRead(book)) {
	// ...
}

class Shelf {
	// ...
	contains = (book) => this.books.includes(book.id);
}

class Reader {
	// ...
	canRead = (book) => this.shelf.contains(book);
}

…код станет гораздо понятнее.

Говорящий метод

Изменилось мало: мы добавили метод, который скрывает внутри себя то, что раньше находилось в условии. Но благодаря ему мы теперь не говорим, как мы хотим что-то сделать; мы говорим, что мы хотим сделать.

Мы начинаем описывать взаимодействие между сущностями через процессы из предметной области. Такое описание облегчает чтение кода, потому что выражает намерение — чего мы хотим добиться в результате.

Подобный метод похож в какой-то мере на фасад, потому что скрывает подробности реализации за собой. Только в нашем случае мы дополнительно переходим от технического языка к понятиям предметной области, оперировать которыми естественнее и проще при описании процессов в системе.

В книге “97 Things Every Programmer Should Know” есть глава “Code in the Language of Domain”. Она как раз описывает подобные случаи.

Инкапсуляция

Также этим методом мы разделили ответственность между модулями. Теперь за работу с книгами отвечает Shelf, так как только ему известно, как правильно проверить наличие книги. Раньше ответственность была размазана по разным модулям, а значит мы могли привести данные в невалидное состояние.

Ссылочки, ссылочки, ссылочки

Ютуб, книга, два конспекта и статья: