Вместо предисловия
Абстрактный Тип Данных (далее АТД) — это набор, включающий данные и выполняемые над ними операции.… Набор данных и методов, служащих одной цели, — это и есть АТД (С. Макконнелл, “Совершенный код”, глава 6.1. Основы классов: абстрактные типы данных). В данном цикле статей будет рассмотрен именно такой взгляд на АТД, расширенный типом данных.
Информация — сведения независимо от формы их представления. Знания относительно фактов, событий, вещей, идей и понятий, которые в определенном контексте имеют конкретный смысл (ISO/IEC 2382:2015).
Данные — это зарегистрированная информация.
Информационные Технологии — это приемы, способы и методы применения средств вычислительной техники при выполнении функций сбора, хранения, обработки, передачи и использования данных (ГОСТ 34.003-90).
Класс, наряду с понятием «объект», является важным понятием объектно-ориентированного подхода в программировании (хотя существуют и бесклассовые объектно-ориентированные языки, например, Прототипное программирование)
Класс является типом данных, определяемым пользователем.
Вычисление — это получение из входных данных нового знания.
Сосотояние характеризуется тем, что описывает переменные свойства объекта. Состояние устойчиво до тех пор, пока над объектом не будет произведено действие. Формальное описание состояний в программировании — набор атрибутов, определяющих поведение объекта.
Действие (формулировка данной статьи) — изменение состояния данных в процессе вычислений и регистрация данного состояния не меняя при этом самих данных которые участвуют в вычислениях. Например: мы меняем состояние автомобиля на плоскости (находился в точке А, переместился в точку Б), при этом данные самого автомобиля мы не затрагиваем. Еще один пример: мы имеем какой либо документ у которого есть срок действия, в процессе вычислений мы получаем состояние документа на какой-то момент времени и фиксируем его, сообщаем/отмечаем, что на такой то момент времени документ действителен/недействителен, при этом сам документ мы не меняем.
Теперь к сути
Если не вдаваться в подробности, согласитесь, весь окружающий нас мир это объекты мира (имена существительные) материальные или нематериальные, параметры (свойства) этих объектов и действия которые можно производить с этими объектами или действия которые эти объекты производят самостоятельно.
Параметры у объектов мира есть всегда! У яблока например это его вкус, его цвет. У цели это ожидаемый результат, параметры достижения результата и т.д. и т.п. Объекты мира могут обладать каким либо действием, а могут и не обладать, но всегда действие можно применить к ним. Например обычная палка не способна самостоятельно производить какие либо действия, но мы можем применить к ней действие, допустим подпереть ею дверь. Автомобиль обладает действием “передвижение на плоскости”, и мы можем применить к нему действие, например завести двигатель или заглушить его. Также мы можем применить действие к автомобилю такое же как он может произвести самостоятельно, переместить его на плоскости (установить параметры нахождения на используемой плоскости).
Из вышесказанного можно сделать вывод — объект мира и его параметры неразделимы, в то время как действие необязательная его черта. Объект мира и его параметры это просто набор данных, зарегистрированная информация о данном объекте. Далее, при написании приложения, мы применяем действие к этим наборам данных.
Т.о. первая задача которая стоит перед нами, это корректное выделение данных из всего нас окружающего, данных к которым будем применять действие.
Научившись корректно выделять данные вам значительно легче будет решать логически сложные задачи, код станет более гибким т.к. изменяя логику работы с одними данными вы не будете задевать другие части программы. Блоки кода будут менее зависимы друг от друга. В голове будет ясность и понимание что да как работает.
Далее, выделенные данные (совокупность объекта мира и его параметров) буду определять как Тип Данных (далее ТД).
ТД (формулировка данной статьи) — атомарная, неделимая единица данных определяемая пользователем, представляющая из себя совокупность объекта мира и его параметров. ТД не способен производить действие.
В концепции ООП ТД это класс, в классе мы определяем поля которые хранят данные и как было написано выше, создавая класс мы создаем новый тип данных определяемый пользователем. СУБД служат для хранения данных и работы с ними — то же самое, нам надо корректно определить данные (сущности БД), с которыми мы в дальнейшем будем работать, применять действие.
Для корректного выделения ТД (данных) из окружающего нас мира определил следующие правила:
- Тип данных должен определять реальную сущность мира, материальную или нематериальную, т.е. быть именем существительным, отвечать на вопрос “кто?” или “что?”
- Если минимально избыточное количество параметров (отсутствие параметров избыточных для вычислений, например для квадрата достаточно знать длину одной стороны т.к. все остальные стороны такой же длины и углы между ними равны 90 градусов) и тип этих параметров у независимых друг от друга предметов (одушевленных или неодушевленных) одинаковые, то эти предметы можно отнести к одному и тому же типу данных. Если минимально избыточное количество параметров отличается или отличаются типы этих параметров, то это различные предметы, а значит и различный тип данных.
До данного момента говорил о данных в ООП и данных в реляционной СУБД, но в них есть существенное отличие, данные в БД не могут производить вычисления сами над собой, это просто зарегистрированная информация, в то время как ТД ООП может производить вычисление со своими данными, хотя принципы выделения ТД одинаковые. Более того, в ООП Классы данных являются плохим тоном и придают коду “запашок”, ТД в ООП должен уметь производить вычисления со своими данными, например для фигуры он должен быть способен вычислить ее площадь.
Может возникнуть закономерный вопрос — чем отличается ТД ООП от АТД по С.Макконнеллу? ТД ООП (по определению данной статьи) может производить вычисления со своими данными, но не может придавать им действие, в то время как АТД может производить вычисления со своими наборами данных, получать результаты вычислений ТД и придавать им действие (формулировку, что такое действие смотрите выше)
Какие есть требования по работе ТД ООП со своими данными смотрите ниже.
Чтобы лучше донести свою мысль, давайте рассмотрим классический пример, Квадрат и Ромб:
И квадрат и ромб являются частным случаем параллелограмма. Обе фигуры соответствуют первому правилу, это параллелограмм, но не соответствуют второму правилу, для квадрата нам достаточен один параметр (длина одной из сторон), тогда как для ромба это два параметра (длина одной из сторон и размер одного из углов). Т.о. это различные типы данных, в ООП эти фигуры должны быть представлены различными классами и различными таблицами в реляционной БД.
У кого-то наверняка появится страх копипаста и он решит представить эти две фигуры в коде или в реляционной БД как одну сущность (один тип данных (класс) в коде ООП или одна таблица реляционной БД) дав ему оба параметра (ширина одной стороны и значение одного из углов) плюс добавив признак квадрат это или ромб. Теперь, для получения площади нашей фигуры в ТД ООП надо добавить условие проверки какую фигуру мы используем и исходя из этого каким способом нам получить ее площадь (в реляционной БД смысл приблизительно такой же, надо добавлять проверки). А потом мы еще вспомним про прямоугольник, он ведь так же является параллелограммом, и нам придется добавлять еще один параметр и еще один признак и еще один условный оператор в методе получения площади фигуры.
Представьте, у нас есть миллиард таких фигур и нам надо получить суммарную их площадь. У каждой фигуры есть метод получения ее площади, нам надо вызвать этот метод миллиард раз. При каждом вызове этого метода условный оператор будет задействован до трех раз, таким образом, для получения площади миллиарда фигур будет вызван условный оператор до трех миллиардов раз. А теперь сравните с другим решением — создаем интерфейс Parallelogram (Параллелограмм) с контрактном (методом) на получения площади этой фигуры и реализуем этот интерфейс для ромба, квадрата и прямоугольника по отдельности. Теперь, при получении площади миллиарда фигур, наш условный оператор не будет задействован ни одного раза, мы просто перебираем все фигуры и у каждой вызываем метод получения ее площади не задумываюсь над тем, что это за фигура. Представляете какую экономию ресурсов вы получите? Да, пускай площадь миллиарда фигур вам никогда не понадобится, но из капли получается река, здесь потеряли ресурсы, в другом месте немного потеряли, в третьем, в итоге приложение будет очень ресурсоемким и дорогим в производительности.
Какие есть требования к ТД:
- ТД должен производить вычисления только со своими данными, иначе на выходе мы можем получить непредсказуемый результат т.к. на входе могут оказаться данные предусмотреть которых мы не могли.
- Данные в ТД не должны зависеть от внешнего состояния приложения работающего с ними, это также может привести к непредсказуемому результату и лишит нас некоторых возможностей, например лишить возможности кешировать результат или использование ТД в другой части ПО, код будет менее подвижен т.к. в другой части программы, которую пишем, могут отсутствовать нужные параметры состояния.
- Данные ТД, участвующие в вычислениях, должны быть неизменяемы на момент работы с ними. Это так же избавит нас от непредсказуемого результата вычислений, когда один метод ТД изменил данные, а другой метод не зная об этом произвел свои вычисления с уже измененными данными, хотя на входе ожидались первоначальные значения. Или например непозволит реализовать паттерн «Легковес»
Эти правила выводимы из самого понятия данных — это зарегистрированная информация. Наверное главная, но не единственная, цель которую преследуют озвученные правила, это предсказуемость результата. Также, придерживаясь этих правил мы можем получить аналог чистых функций из функционального программирования, что из этого следует будет описано в следующей статье т.к. методы работы с данными будут рассмотрены в ней.
ВАЖНО: в первую очередь надо бояться непредсказуемого изменения данных, т.е. при использовании в вычислениях данные не должны меняться (требование к ТД №3), если же вы намеренно меняете их, то это изменение предсказуемо и вы можете произвести какие либо действия для получения в дальнейшем корректного результата.
На первый взгляд может показаться что я вступил в противоречие с самим собой — данные должны быть неизменяемыми, изменения данных должны быть предсказуемы. Просто я забегаю немного вперед, предсказуемый результат больше относится ко второй части (управление данными), но как бороться с непредсказуемостью решил описать здесь, т.к. ТД также имеет методы работы со своими данными (полями) и поэтому эти правила распространяются также и на него.
И так, правила для достижения неизменяемости (стабильности) данных:
- С. Макконнелл, “Совершенный код”, глава 7.5 “Советы по использованию параметров методов” — “Не используйте параметры метода в качестве рабочих переменных. Создайте для этой цели локальные переменные”. Т.о. вы избежите непредсказуемого изменения данных вне использования метода, например если вы передали параметр по ссылке, то изменив их в методе они изменятся так же и для другого кода класса, что потенциально ведет к ошибке. Хотя С.Макконнелл говорит несколько о другом эффекте, непредсказуемость значения внутри метода, но смысл тот же, стремление избежать непредсказуемости результата.
- В приватных методах класса не используйте внутренние поля напрямую, передавайте их в качестве параметров этого метода. Т.о. вы избежите случайного/непредсказуемого изменения данных, когда один метод изменил данные, а другой не зная об этом приступил к вычислениям над ними.
- В методах класса не обращайтесь к внутренним полям напрямую, используйте для этого специально созданные методы. Т.о. вы получите единую точку входа для работы с внутренними полями и полный контроль над ними.
- Стремитесь к использованию приватных методов, С.Макконнелл, “Совершенный код”, глава 5 — Почаще задавайте себе вопрос «Что мне скрыть?», и вы удивитесь, сколько проблем проектирования растает на ваших глазах.
Никоем образом не хочу сказать, что это все является панацеей, конечно же есть и другие способы решения сложных задач, волшебной таблетки не существует, будьте гибче, применяйте в своей работе разные подходы. Приведу также в пример цитату одной из моих любимых статей на хабре “Топ-11 самых частых ошибок в JavaScript” — ошибка №11: Ты следуешь всем правилам, правила для того, чтобы их ломать, если вы понимаете, почему нельзя использовать тот или иной прием, то он становится инструментом, который вы можете правильно применять в правильной ситуации.
Итог:
- ПО работает с данными, придает им действие или производит с ними действие. Данные, это зарегистрированная информация.
- ТД — атомарная, неделимая единица данных определяемая пользователем, представляющая из себя совокупность объекта мира и его параметров. ТД не способен производить действие.
- Для корректного определения Типа Данных (выделения данных) применяйте следующие правила:
1) Тип данных должен определять реальную сущность мира, материальную или нематериальную, т.е. быть именем существительным, отвечать на вопрос “кто?” или “что?”
2) Для одного типа данных минимально избыточное количество и тип параметров должны быть одинаковыми.
- ТД должен уметь производить вычисления только со своими данными (полями).
- Данные ТД не должны зависеть от внешнего состояния.
- Данные ТД, участвующие в вычислениях, должны быть неизменяемы на момент работы с ними.
- Бойтесь/избегайте непредсказуемого изменения данных.