Книга Егора Бугаенко Elegant Objects поднимает проблему поддерживаемости кода. Это большая редкость среди того огромного количества книг по программированию, которые учат просто писать код, не задумываясь о том, что с ним будет дальше.

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

В [1] Егор рассуждает о том, почему не следует использовать разделяемые константы. Вкратце его аргументы такие. Разделяемые константы: 1) добавляют связанности между объектами; 2) не имеют никакой смысловой нагрузки, т.к. они оторваны от контекста; 3) не являются антропоморфными объектами.

Вообще, антропоморфность, наиболее ярко описанная в [2], не является необходимым свойством объекта. Для Гради Буча, например, объект – сущность обладающая поведением [3]. У констант поведения, понятное дело, нету.

Константы в Java – это просто разделяемые значения, которые применяются в различных частях программы. Поэтому Егор предлагает создавать объекты, инкапсулирующие константу внутри себя, и применяющие её при помощи собственных методов. Это такой inversion of control, когда не ваш код использует константу, а обращаетесь к константному объекту, чтобы он что-то сделал для вас.

Вместо такого:

public class Constants {
   public static final String NEWLINE = "\r\n";
 }

. . .

System.out.println("Hello" + Constants.NEWLINE);

должно быть такое:

public class NewLine {
   public String apply(String src) {
     return src + "\r\n";
   }
 }

. . .

System.out.println(new NewLine().apply("Hello"));

Это интересный подход, здорово напоминающий числа Чёрча [4], где число представляется не значением n, а функцией, которая применяется n раз. Проблема такого подхода в том, что вместо нескольких классов, содержащих в себе нужные константы, появится гигантское количество константных классов, в которых будет крайне сложно разобраться.

Егор пишет: “I do mean that the more classes you have in your application, the better its design and more maintainable it is”.

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

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

Чтобы проиллюстрировать проблему на примере Java-кода, взгляните хотя бы, на код фреймворка Takes, написанный как раз в таком стиле. Посмотрите и попробуйте разобраться, как сформировать нужный вам Request.

На конференции JavaDay’2016 в Минске Егору задали вопрос, как он борется вот с этой крайностью. Ответ Егора был такой, что если кому что не понятно во фреймворке, то ему задают вопросы и он тогда отвечает. И Егор признает проблему, но считает, что просто пока не разработали нужные инструменты, позволяющие быстро разобраться, как комбинировать такие маленькие классы.

В своей статье о Haskell я уже писал, сколько проблем добавляет чрезмерное количество типов. Даже на то, чтобы разобраться с какой-нибудь маленькой и простой библиотекой может уйти уйма времени, если в ней используется пара десятков типов. А уж чтобы применить их правильно – так на это могут уйти часы борьбы с ошибками компиляции. Поэтому здесь из своего опыта я утверждаю, что большое количество маленьких классов является не меньшей проблемой, чем малое количество больших классов.

Между прочим, в академической среде тоже осознали эту проблему. Ведутся уже даже не теоретические, а вполне практические разработки [5], упрощающие поиск подходящего типа среди доступных. Но, прошу заметить, речь идет о совершенно новом языке программирования Idris, чрезвычайно сложном и супер-модерновом. До Java эти разработки дойдут еще ох как не скоро, если вообще дойдут в силу своей непомерной сложности.

Есть и другой подход, полностью противоположный, ставящий во главу угла простоту. В Clojure предлагается не создавать новые типы на всё подряд, а пользоваться существующими: списками, векторами, отображениями, множествами. Лично мне такой подход нравится намного больше по очевидной причине: чтобы воспользоваться какой-то функцией, не нужно бесконечно разбираться, какие типы или их комбинации к ней подходят. В Clojure часто достаточно взглянуть на один единственный пример – и уже всё понятно. Конечно, можно неправильно воспользоваться и стандартными типами, но это можно по крайней мере выяснить “методом научного тыка”. И несравненно дольше разбираться вот с такой, например, сигнатурой, где каждый параметр представляет свой тип, с которым не сразу еще и поймёшь как работать:

http2 :: Connection -> InternalInfo1 -> SockAddr -> Transport -> S.Settings -> (BufSize -> IO ByteString) -> Application -> IO ()

А вот пример чего-то похожего на Clojure, здесь вообще используются одни лишь hash-map-ы:

(defn dummy-app [req] {:body "It works" :status 200})

 (jetty/run-jetty dummy-app {:port 5000
                             :h2c? true
                             :h2? true
                             :ssl? true
                             :ssl-port 5443
                             :keystore "..."
                             :key-password "..."})

В целом, конечно, ‘Elegant Objects’ – очень хорошая книга для думающих программистов. Если честно, то у меня сложилось впечатление, что для ООП в понимании Егора язык Clojure или даже Haskell подходит гораздо больше, чем Java. Если переписать примеры из ‘Elegant Objects’ на Clojure, то, во-первых, и код станет короче, и, во-вторых, половину книги можно будет пропустить, т.к. в Clojure просто нету тех проблем, из-за которых книга и написана.

Литература

1. Bugayenko Ye., 2.5. Don't use public constants. In: Elegant Objects v1.1. Location: CreateSpace, 2016, pp.64-73.
2. D.West, Observing the Object Difference. Location: Object Thinking, Microsoft Press, 2004.
3. Буч Г., Глава 2. Объектный подход. В кн.: Объектно-ориентированное проектирование с примерами применения, Москва, "И.В.К", 1992, с. 38.
4. Пирс Б., 5.2. Программирование на языке лямбда-исчисления. В кн.: Типы в языках программирования, Москва, 2010, с.80
5. Brady E., 1.2.4 Incomplete Definitions: Working with Holes. Location: Type-Driven Development with Idris, Manning, 2016, pp.7-9