Fluent Interfaces Are Bad for Maintainability

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

Fluent interface, впервые использованный в терминологии Мартином Фаулером, представляет собой очень удобный способ взаимодействия с объектами в ООП. Он делает их фасады более простыми в использовании и понимании. Однако он разрушает их внутреннюю структуру, делая их более сложными для поддержки. Несколько слов на эту тему были сказаны Марко Пиветтой в его блоге Fluent Interfaces are Evil; теперь я добавлю свои несколько слов.

Возьмем мою собственную библиотеку jcabi-http, которую я создал несколько лет назад, когда считал, что fluent интерфейсы - это хорошая вещь. Вот как вы используете библиотеку для выполнения HTTP-запроса и проверки его вывода:

Этот удобный цепочный метод делает код коротким и понятным, не так ли? Да, на первый взгляд. Но внутреннее устройство классов библиотеки, включая JdkRequest, который вы видите, далеко не является элегантным. Самая большая проблема заключается в том, что они достаточно большие, и их невозможно расширить без увеличения их размера.

Например, в данный момент у JdkRequest есть методы method(), fetch() и несколько других. Что произойдет, когда потребуется новая функциональность? Единственный способ добавить ее - сделать класс больше, добавив новые методы, что подрывает его поддерживаемость. Здесь, например, мы добавили multipartBody(), а здесь мы добавили timeout().

Я всегда чувствую страх, когда получаю новый запрос о функциональности в jcabi-http. Я понимаю, что это, скорее всего, означает добавление новых методов в Request, Response и другие уже раздутые интерфейсы и классы.

Фактически, я попытался сделать что-то в библиотеке, чтобы решить эту проблему, но это было не просто. Взгляните на этот вызов метода .as(RestResponse.class). Он декорирует Response с RestResponse, чтобы сделать его более богатым методами. Я просто не хотел, чтобы Response содержал более 50 методов, как это делают многие другие библиотеки. Вот что он делает (это псевдокод):

Как видите, вместо добавления всех возможных методов в Response я поместил их в дополнительные декораторы RestResponse, JsonResponse, XmlResponse и другие. Это помогает, но чтобы написать эти декораторы с центральным объектом типа Response, нам нужно использовать этот “некрасивый” метод as(), который сильно зависит от рефлексии и приведения типов.

Другими словами, “плавные” интерфейсы означают большие классы или некоторые некрасивые обходные пути. Я уже упоминал об этой проблеме ранее, когда писал об API потоков и интерфейсе Stream, который отлично “плавает”. В нем 43 метода!

Это самая большая проблема “плавных” интерфейсов - они заставляют объекты становиться огромными.

“Плавные” интерфейсы идеальны для их пользователей, так как все методы находятся в одном месте, и количество классов очень небольшое. Их легко использовать, особенно с автозаполнением кода в большинстве сред разработки. Они также делают клиентский код более читаемым, так как “плавные” конструкции выглядят подобно простому английскому языку (также известному как DSL).

Все это правда! Однако ущерб, который они наносят объектному дизайну, является слишком высокой ценой.

Какова альтернатива?

Я рекомендую вам использовать декораторы и умные объекты вместо этого. Вот как я бы разработал jcabi-http, если бы мог сделать это сейчас:

Это тот же код, что и в первом отрывке выше, но он намного более объектно-ориентированный. Очевидная проблема с этим кодом, конечно, заключается в том, что IDE не сможет автозаполнить практически ничего. Кроме того, нам придется запоминать много имен классов. И конструкция выглядит достаточно сложной для тех, кто привык к плавным интерфейсам. Кроме того, она очень далека от идеи DSL.

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

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

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

Кроме того, очень часто я заинтересован в расширении существующей функциональности как внутри моей кодовой базы, так и через запрос на объединение в библиотеке. Мне намного интереснее делать это, если я знаю, что внесенные изменения изолированы и легко тестируются.

Таким образом, у меня больше нет плавных интерфейсов, только объекты и декораторы.

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-05 at 21:26

sixnines availability badge   GitHub stars