среда, 3 мая 2017 г.

Объектно-ориентированное вранье



В Java всегда одно и то же. Сначала все красиво, приложение маленькое и все понятно, вот классы, вот методы. Но потом все хуже и хуже,  кода становится больше, код становится громоздким и постепенно превращается в ту гадость, которую уже трудно поддерживать и он просто выкидывается.... к нему уже совсем не интересно прикасаться чтобы разгребать эту кучу. Сломался maintainability кода.

Почему же так происходит? Книга Object Thinking Дэвида Уэста дает ответ. Эта книга должна стать настольной книгой, ведь, в ней показывается, что проблема в том, что мы (программисты) продолжаем думать процедурно и, соответственно, когда создается большой проект все ограничения процедурного программирования и вылезают наружу. 

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

Вместо того, как думать объектно, мы продолжаем думать как нас научили в C и Assembler, думать как компьютер, думать командой за командой. При таком подходе компьютер всегда первичен, а программист вторичен.

И вот ОООП и, к стати, функциональное программирование тоже, были придуманы, чтобы поставить на первое место программиста, человеческое мышление.

Spring, Hibernate, PoJo, utility классы, design-паттерны и т.п. Это все вредит, это процедурное мышление. Они заставляют нас думать не объектно.

Самое  главнное (для революции) - определить, что такое объект. В стандартном понимании - это данные, к которым прикреплены функции, но это, опять же, процедурное мышление. Тогда как, более разумно считать объект - это живым, живой сущностью со свойствами, на которые мы можем полагаться. У объекта есть свои интересы, это не просто глупая структура, которой мы манипулируем как хотим.

3 проблемных места в джаве, которые мешают нам: геттеры, сеттеры, статические методы - это все, зло!
  • геттеры - категорически неправильный дизайн
Используя геттеры мы нарушаем инкапсуляцию, т.к. объект теряет контроль над собственными данными...  он их отдает, он не делает сам что-то... в него влезли вовнутрь и выковырнули их... Якобы геттеры защищают данные, но это не так. Их же все равно забирают изнутри.

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

Вместо того чтобы попросить объект сделать для нас мы геттером влезаем вовнутрь объекта. Более же правильно - поручать объекту ответственность. Например, объект - представитель файла, он он не хранитель имени файла, которое у него можно выковырнуть, он представляет способ работы с файлом, именно он получает из файла информацию и предоставляет ее нам. При этом соблюдается идея инкапсуляции - то, что должно быть внутри, снаружи мы трогать не можем!!!
  • сеттеры - провоцируют на mutable классы
У нас сначала создаем объект, потом инициализируем, потом еще раз меняем значение через set, но это неполноценно. Это провоцирует на создание mutable (изменяемых) классов, но immutable гораздо проще технически, они меньше/компактнее, удобнее в работе.

объект - представитель объекта в реальной жизни, и 

когда мы вторгаемся в объект, мы заставляем его совершить предательство ответственности, и, соответственно, мы больше не доверяем объекту и значит опять провоцируем процедурное мышление. Вместо того чтобы объект решал вопрос мы перетягиваем контроль на себя. Но объекту нужно доверять... чтобы он занимался только своим одним делом.
  • статический метод - у нас нет объекта, и это проблема, опять набор процедур
Когда мы используем метод, который не привязан ни к какому объекту (достаточно лишь обратиться к классу), мы вообще теряем ОО концепцию.

utility классы - это одни процедуры..

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

Пример правильного объекта, у которого только одно свойство - получить контент... объект может предоставить нам контент и это его свойство.

class File{
private final  String path;
//еще, конечно, должен быть конструктор, но для примера он не нужен
String content(){
//read and return
}
}

У Егора Бугаенко более 45 рекомендаций, как сделать Java код более объектно ориентированным и 23 из них представлены в его книге Elegant Objects. Также, много информации содержит блог Егора.


----------------------------
Вопрос №1:

Кажется, этот подход нарушает SRP (single responsibility princip) - принцип одной ответственности, ведь, получается, что, наш объект, например, отвечающий за работу с файлом, должен уметь про себя все... 

Ага, верно, объект должен все про себя уметь, а дальше мы смотрим, что значит все... На самом деле нам важно чтобы в файле(объекте) был контент, это самое главное, а вот все дополнительные вещи - это уже декораторы.


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

Вертикальное полотно утилити методов мы разворачиваем в иерархию декораторов. Что-то простое в сердцевине, а дальше мы это оборачиваем в что-то более сложное.

Вот, например, у нас 4 метода которые что-то могут делать с контентом, мы можем создать из них 4 статических метода, а можем 4 класса под каждый из этих функционалов, и каждый из них заворачивает исходный объект.

---
Вопрос №2:

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

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

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

...
Вопрос №3:

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

Ну, в этом случае нам нужен rename, но с результатом новый фаил. Не влезать в уже существующий объект, нет, мы пересоздаем объект и уже работаем с новым.

А если конструктор слишком тяжелый (и создавать новые объекты дорого)?

Ага и начинаем думать, а почему конструктор такой тяжелый... значит что то не то... у нас то классы всегда маленькие. Чисто интуитивно, если классы маленькие, то мы не поставим в конструктор 10 параметров потому что это будет не удобно некрасиво и мы поймем что совершаем ошибку

Если у нас mutable классы? то мы в них постоянно пихаем новый функционал, и наши классы разрастаются и становятся просто огромными, если же immutable то при каждом измении мы возвращаем новый объект, конечно используя конструктор, но значит и параметров должно быть 2-3. Если же пихаем 5-6-7 параметров, то уже что то не то - нас это остановит, нам нужно разбивать класс и наше текущее понимание проблемы не верное и то как мы продизайнили классы не соответствует реальности, поэтому нам нужно вернуться на маленькие классы.

чем меньше ваши классы по размеру, и чем больше их количество - тем качественнее ваш код, у каждого класса не более 4 публичных паблик методов... приват сколько угодно, а вот если публичных 10 то выкинуть его... их должно быть мало и они должны отвечать за какую-то одну фичу, и отвечать за одну функцию. 

Много классов хорошо, но конечно, если они не дублируют друг-друга и там нет копипаста.

...
Вопрос №4:

null - это вообще одна большая ошибка и его вообще не должно быть , его нужно избегать, но что делать, если у нас не полные входящие параметры?

ага игра, например, и там объект может присутствовать, а может и нет

ну, если наш класс может принимать в том числе Null объекты, это означает что дизайн класса слишком абстрактный, на все случаи жизни

например в нашем примере чтения из файла, у нас кроме самого чтения например есть еще свойство кодировки, и если кодировка указана то одно, а если не указана то другое - так это ошибка, это нарушает single responsibility принцип, так наш класс может читать и с кодировкой и без - это на самом деле 2 класса и мы не должны инкапсулировать null

то же самое в игре, мы не должны их сохранять внутри и говорить о том как ему с этим null жить дальше

if null создать один класс if не null создать другой
если слишком много свойств, то много классов - хороший выход и это прекрасно, так и должно быть

нужно думать, как создавать классы, это на самом деле не просто, придумать им всем имена, и чтобы они не дублировали друг друга, это сложно, но, когда вы это сделаете, уйдете от null, сеттеров и геттеров, стат методов, вам на многие годы вперед ваш код будет легко поддерживать. Это не просто, но когда вы пойдете по этому пути, дальше будет только счастье.


Комментариев нет:

Отправить комментарий