The Hidden Dangers of Method Overloading

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

Перегрузка методов - это распространенная возможность во многих языках программирования, которая позволяет классу иметь два или более методов с одним именем, но с разными параметрами. Согласно информации от Microsoft, перегрузка методов является “одной из наиболее важных техник для улучшения удобства использования, производительности и читаемости повторно используемых библиотек”. Я не согласен с этим. По моему мнению, перегрузка методов может привести к менее читаемому коду и большему количеству ошибок, потому что поддержка двух или более реализаций с одним именем приводит к скрытой семантике, что неизбежно приводит к непониманию и функциональным дефектам.

Давайте начнем с примера на Java. Предположим, что вы хотите разрешить добавление товара в корзину покупок, имея в своем распоряжении либо идентификатор товара, либо объект Product. Если предоставлен только идентификатор, вы хотели бы, чтобы корзина покупок получила доступ к каталогу товаров, нашла соответствующий объект Product и добавила его. Вот где перегрузка методов может оказаться полезной (метод add() определен дважды с двумя разными сигнатурами и реализациями):

class Cart {
  private final List<Product> products;
  void add(int id) {
    this.add(new Catalog().findById(id));
  }
  void add(Product p) {
    this.products.add(p);
  }
}
var c = new Cart();
c.add(new Product("book"));
c.add(42);

Этот подход действительно удобен по нескольким причинам. Во-первых, метод add(int) обрабатывает преобразование из int в Product, которое не требуется повторять в других местах — они могут просто передать идентификатор продукта в этот метод и позволить ему сделать всю работу, тем самым устраняя дублирование кода. Во-вторых, поскольку функциональность “поиска в каталоге” не выходит за пределы класса Cart, это упрощает окружающий код. Кажется, что действительно улучшается удобство использования и читаемость кода.

Однако, проблемы, которые решаются (дублирование кода и сложность), меньше, чем проблемы, которые вносятся. В то время как семантика метода add(Product) очевидна, работа метода add(int) неясна для его пользователей. Возможно, он ищет в каталоге? Может быть, он выбирает n-й продукт из существующей корзины и добавляет его в конец корзины? Или, может быть, он ищет заказы, ранее размещенные пользователем, и извлекает n-й продукт оттуда? Мы просто не знаем, когда изучаем сигнатуру метода.

Для того чтобы понять, что делает метод add(int), мы должны обратиться к его блоку Javadoc, который может быть недостаточно точным. Более того, как часто бывает, документация в блоке Javadoc может не соответствовать коду внутри метода. Проще говоря, клиент, использующий метод add(int), неизбежно делает предположение о его внутренней работе. Если клиенту повезёт, 1) предположение будет верным, и 2) любые последующие изменения метода не нарушат это предположение.

Кажется, что перегрузка методов является источником проблемы: поддержка нескольких реализаций под одним именем неизбежно вводит скрытую семантику в большинстве случаев. Затем, когда что-то затуманивается, это вызывает предположения, которые в свою очередь приводят к недоразумениям и в конечном итоге к ошибкам.

Я считаю, что лучшей альтернативой будет выглядеть следующим образом:

class ProductInCatalog implements Product {
  private final Product p;
  ProductInCatalog(int id) {
    this.p = new Catalog().findById(id);
  }
  // all "Product" interface's methods
}
class Cart {
  private final List<Product> products;
  void add(Product p) {
    this.products.add(p);
  }
}
var c = new Cart();
c.add(new Product("book"));
c.add(new ProductInCatalog(42));

Конструктор ProductInCatalog не является без кода, как было бы идеально, однако, это не так важно для нашего текущего обсуждения. Класс ProductInCatalog служит абстракцией Product, найденного в каталоге. Этот класс используется клиентом класса Cart. Клиент, полностью осознавая и с явным намерением, преобразует 42 (ID продукта) в экземпляр ProductInCatalog. Этот дизайн больше не скрывает никаких элементов. Нет необходимости делать предположения и нет условий, которые код должен выполнять.

Мы все еще сохраняем преимущества, предоставленные перегрузкой методов? Действительно, да. Нет дублирования кода и сложность кода снижается. Кроме того, улучшается читаемость кода с использованием Cart. Подводя итог, я рекомендую избегать перегрузки методов, хотя это, безусловно, приведет к большему количеству классов в кодовой базе. Однако это уже совсем другая дискуссия.

Translated by ChatGPT gpt-3.5-turbo/35 on 2023-09-08 at 16:30

sixnines availability badge   GitHub stars