Абстрактный тип данных. Часть 1: Данные (Тип Данных)

от автора

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

Вместо предисловия

Абстрактный Тип Данных (далее АТД) — это набор, включающий данные и выполняемые над ними операции (С. Макконнелл, “Совершенный код”, глава 6.1. Основы классов: абстрактные типы данных). В данном цикле статей будет рассмотрен именно такой подход к АТД.

Информация — сведения независимо от формы их представления. Знания относительно фактов, событий, вещей, идей и понятий, которые в определенном контексте имеют конкретный смысл (ISO/IEC 2382:2015).

Данные — это зарегистрированная информация.

Информационные Технологии — это приемы, способы и методы применения средств вычислительной техники при выполнении функций сбора, хранения, обработки, передачи и использования данных (ГОСТ 34.003-90).

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

Если не вдаваться в подробности, согласитесь, весь окружающий нас мир это объекты мира (имена существительные) материальные или нематериальные, параметры (свойства) этих объектов и действия которые можно производить с этими объектами или действия которые эти объекты производят самостоятельно.

Параметры у объектов мира есть всегда! У яблока например это его вкус, его цвет. У цели это ожидаемый результат, параметры достижения результата и т.д. и т.п. Объекты мира могут обладать каким либо действием, а могут и не обладать, но всегда действие можно применить к ним. Например обычная палка не способна самостоятельно производить какие либо действия, но мы можем применить к ней действие, допустим подпереть ею дверь. Автомобиль обладает действием “передвижение на плоскости”, и мы можем применить к нему действие, например завести двигатель или заглушить его. Также мы можем применить действие к автомобилю такое же как он может произвести самостоятельно, переместить его на плоскости (установить параметры нахождения на используемой плоскости).

Из вышесказанного можно сделать вывод — объект мира и его параметры неразделимы, в то время как действие необязательная его черта. Объект мира и его параметры это просто набор данных, зарегистрированная информация о данном объекте. Далее, при написании приложения, мы производим вычисления с этими данными, т.е. применяем действие к ним.

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

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

Для этих целей вывел для себя правила:

  1. Тип данных должен определять реальную сущность мира, материальную или нематериальную, т.е. быть именем существительным, отвечать на вопрос “кто?” или “что?”
  2. Если минимально избыточное количество параметров (отсутствие параметров избыточных для вычислений, например для квадрата достаточно знать длину одной стороны т.к. все остальные стороны такой же длины и углы между ними равны 90 градусов) и тип этих параметров у независимых друг от друга предметов (одушевленных или неодушевленных) одинаковые, то эти предметы можно отнести к одному и тому же типу данных. Если минимально избыточное количество параметров отличается или отличаются типы этих параметров, то это различные предметы, а значит и различный тип данных.

До данного момента говорил о данных в ООП и данных в реляционной СУБД, но в них есть существенное отличие, данные в БД не обладают действиями, это просто зафиксированная информация, в то время как ТД ООП можно наделить действиями, хотя принципы выделения ТД одинаковые. Более того, в ООП Классы данных являются плохим тоном и придают коду “запашок”, ТД должен уметь работать со своими данными, например для фигуры он должен быть способен вычислить ее площадь, хотя и это не аксиома, к примеру для объекта-параметра.

Будьте гибче!

Какие есть требования по работе объекта ООП со своими данными смотрите ниже.

Чтобы лучше донести свою мысль, давайте рассмотрим классический пример, Квадрат и Ромб:
И квадрат и ромб являются частным случаем параллелограмма. Обе фигуры соответствуют первому правилу, это параллелограмм, но не соответствуют второму правилу, для квадрата нам достаточен один параметр (длина одной из сторон), тогда как для ромба это два параметра (длина одной из сторон и размер одного из углов). Т.о. это различные типы данных, в ООП эти фигуры должны быть представлены различными классами и различными таблицами в реляционной БД.

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

Представьте, у нас есть миллиард таких фигур и нам надо получить суммарную их площадь. У каждой фигуры есть метод получения ее площади, нам надо вызвать этот метод миллиард раз. При каждом вызове этого метода условный оператор будет задействован до трех раз, таким образом, для получения площади миллиарда фигур будет вызван условный оператор до трех миллиардов раз. А теперь сравните с другим решением — создаем интерфейс Parallelogram (Параллелограмм) с контрактном (методом) на получения площади этой фигуры и реализуем этот интерфейс для ромба, квадрата и прямоугольника по отдельности. Теперь, при получении площади миллиарда фигур, наш условный оператор не будет задействован ни одного раза, мы просто перебираем все фигуры и у каждой вызываем метод получения ее площади не задумываюсь над тем, что это за фигура. Представляете какую экономию ресурсов вы получите? Да, пускай площадь миллиарда фигур вам никогда не понадобится, но из капли получается река, здесь потеряли ресурсы, в другом месте немного потеряли, в третьем, в итоге приложение будет очень ресурсоемким и дорогим в производительности.

Какие есть требования к ТД:

  1. ТД должен уметь работать только со своими данными, иначе на выходе мы можем получить непредсказуемый результат т.к. на входе могут оказаться данные предусмотреть которых мы не могли.
  2. Данные в ТД не должны зависеть от внешнего состояния приложения работающего с ними, это также может привести к непредсказуемому результату и лишит нас некоторых возможностей, например лишить возможности кешировать результат или использование ТД в другой части ПО, код будет менее подвижен т.к. в другой части кода могут отсутствовать нужные параметры состояния.
  3. Данные должны быть неизменяемые (immutable), это так же может привести к непредсказуемому результату и лишить нас некоторых возможностей, например лишит возможности кэширование результат или реализовать паттерн “Легковес” и т.п. Александр Швец, “Погружение в паттерны проектирования”, паттерн “Легковес” — неизменяемые данные объекта принято называть “внутренним состоянием”. К этому и надо стремиться, ТД должно обладать только внутренним состоянием.

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

ВАЖНО: в первую очередь надо бояться непредсказуемого изменения данных, т.е. при использовании в вычислениях данные не должны меняться, если же вы намеренно меняете данные, то это изменение предсказуемо и вы можете произвести какие либо действия для получения в дальнейшем корректного результата.

На первый взгляд может показаться что я вступил в противоречие с самим собой — данные должны быть неизменяемыми, изменения данных должны быть предсказуемы. Просто я забегаю немного вперед, предсказуемый результат больше относится к части “управление данными”, но как бороться с непредсказуемостью решил описать здесь, т.к. ТД также имеет методы работы со своими данными (полями) и поэтому эти правила распространяются также и на него.

И так, правилами для достижения неизменяемости (стабильности) данных:

  1. С. Макконнелл, “Совершенный код”, глава 7.5 “Советы по использованию параметров методов” — “Не используйте параметры метода в качестве рабочих переменных. Создайте для этой цели локальные переменные”. Т.о. вы избежите непредсказуемого изменения данных вне использования метода, например если вы передали параметр по ссылке (не намеренно), то изменив их в методе они изменятся так же и для другого кода класса, что потенциально ведет к ошибке. Хотя С.Макконнелл говорит несколько о другом эффекте, непредсказуемость значения внутри метода, но смысл тот же, стремление избежать непредсказуемости результата.
  2. В приватных методах класса не используйте внутренние поля напрямую, передавайте их в качестве параметров этого метода. Т.о. вы избежите случайного/непредсказуемого изменения данных, когда один метод изменил данные, а другой не зная об этом приступил к вычислениям над ними.
  3. В методах класса не обращайтесь к внутренним полям напрямую, используйте для этого специально созданные методы. Т.о. вы получите единую точку входа для работы с внутренними полями и полный контроль над ними.
  4. Стремитесь к использованию приватных методов, С.Макконнелл, “Совершенный код”, глава 5 — Почаще задавайте себе вопрос «Что мне скрыть?», и вы удивитесь, сколько проблем проектирования растает на ваших глазах.

Никоем образом не хочу сказать, что это все является панацеей, конечно же есть и другие способы решения сложных задач, волшебной таблетки не существует, будьте гибче, применяйте в своей работе разные подходы. Приведу также в пример цитату одной из моих любимых статей на хабре “Топ-11 самых частых ошибок в JavaScript” — ошибка №11: Ты следуешь всем правилам, правила для того, чтобы их ломать, если вы понимаете, почему нельзя использовать тот или иной прием, то он становится инструментом, который вы можете правильно применят в правильной ситуации.

Итог:

  1. ПО работает с данными, придает им действие или производит с ними действие. Данные, это зарегистрированная информация.
  2. Под данными (Тип Данных) подразумевается объект мира в совокупности с его параметрами.
  3. Для корректного определения Типа Данных (выделения данных) применяйте следующие правила:

    1) Тип данных должен определять реальную сущность мира, материальную или нематериальную, т.е. быть именем существительным, отвечать на вопрос “кто?” или “что?”
    2) Для одного типа данных минимально избыточное количество и тип параметров должны быть одинаковыми.

  4. ТД должен уметь работать только со своими данными (полями)
  5. Внутреннее состояние ТД должно быть неизменяемое и не зависеть от внешнего состояния.
  6. Бойтесь/избегайте непредсказуемого изменения данных.

Используемая литература, ссылки:

  1. С. Макконнелл, “Совершенный код”
  2. Александр Швец, “Погружение в паттерны проектирования”
  3. Функционвльное программирование
  4. Классы данных
  5. объекта-параметра

ссылка на оригинал статьи https://habr.com/ru/post/506170/