Привет!

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

DoK. Мысли из жизни. Rss

Проблема редактирования и состояния объектов

Posted by DoK | Posted in Uncategorized | Posted on 19-04-2010

Как работает абстрактный редактор в GUI?

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

А вот что, если отмена? По идее ничего, просто не надо ничего применять. Объект остаётся в исходном состоянии.

И вот тут начинаются сложности, как бы так не изгадить исходный объект. Вариант первый, можно использовать своего рода «патчи» – т.е. редактор накапливает изменения и применяет их _всегда_ только, если все проверки пройдены.

Проблемы:
- Если какие-то проверки целостности завязаны на совокупное состояние объекта (скажем, что поле А меньше поля Б), то логику проверки придётся продублировать в диалоге. Нельзя использовать зашитое в объекте, т.к. нельзя в него записать (вдруг проверка не пройдёт и будет «отмена»).

- И это бы ладно, но что делать, если редактор в том числе вносит изменения в списковое поле?

Пример:
Редактируем партнёра
+ гридик с его юр-лицами.

Соответственно в процессе редактирования партнёра юр. лица могут добавляться, удаляться, изменяться. При изменении юр. лица показывается редактор юр. лица (так или иначе) и он вообще говоря знать не знает про то, что он показан в контексте редактирования партнёра и ему надо накопить изменения, но не применять (т.к. редактирование партнёра может и отмениться).

В общем на списках у меня эта стратегия работы с накоплением изменений и загнулась.

Альтернативно сейчас думаю над вариантом, когда редактор начиная что-то править делает себе полную копию того, что ему дали (а это ой как не просто, сделать полную копию :( ), и делает с ней что хочет, если всё успешно – возвращает и это уже обязанность вызвавшего правильно «пристроить» эту копию – записать себе в список, сохранить в БД или ещё чего с ней сотворить.

Отдельно есть сложность составления копии… тут уже весёлости Java. В общем, с clone всё очень непросто, там и жёсткие проверки не сделаешь и с теми же коллекциями хлопот много.

В общем из способов не слишком обременительных пока нарыл Serializable + засериализовать/рассериализовать… криво, сначала даже думать не хотел в эту сторону, но всё остальное ещё хуже.

В общем это так, поток мысли в процессе. Кто что скажет?

Пустячок с LEFT JOIN или NULL != NULL is false и NULL = NULL is false

Posted by DoK | Posted in Uncategorized | Posted on 10-02-2010

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

Простой запрос:

  1. SELECT o.*, m.*
  2. FROM
  3.   orders o
  4.   LEFT JOIN manager m ON m.id = o.manager_id
  5. WHERE
  6.   m.is_active = true

Запрос простой, тут нестыковка очевидна – несмотря на то, что хочу я получить все заказы, с активными менеджерами, либо заказы без менеджера (т.к. LEFT JOIN), получу я только лишь заказы с активными менеджерами. Проблема в том, что там где менеджер не найдётся (предположим это необязательное поле), m.is_active будет NULL, а когда я сравню NULL c true, я получу ложь. И такие записи меня не устроят.

Решение? Обходное – m.is_active is null or m.is_active = true. Правильное? Вероятно здесь надо просто перенести условие в условие JOIN-а (на самом деле в зависимости от того, что я имел ввиду).

Область видимости переменных

Posted by DoK | Posted in Uncategorized | Posted on 08-02-2010

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

Самое простое в программе – локальные переменные. Почему? Они живут на стэке => существует один экземпляр для одного вызова функции. Вся работа с ними у нас перед глазами – все изменения и чтения => мы можем легко добавлять новые, менять и убирать старые. Всё у нас в руках. Есть полная уверенность, что ни одна другая часть программы не может влиять на них.

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

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

Поля экземпляра. Тут уже всё несколько сложнее. Проблем несколько:

  1. Чтобы понять, какие поля использует метод надо прочитать его. Т.е. нигде в его заголовке это не описано.
  2. Доступ к полям имеют все методы, а значит они могут их менять. Это означает, что на протяжении работы нашего метода, если он не дай бог вызывает что-то ещё, значение поля может измениться.
  3. Значение поля одно на один экземпляр, а значит при рекурсии могут быть проблемы.

Таким образом, при работе с неконстантными полями объекта в голове надо держать уже весь класс целиком (все методы, работающие с полем), всех предков (если поле унаследовано) и всех потомков (если поле не private). А это довольно сильно повышает нагрузку на мозг. Не всё так страшно, если использовать несколько правил:

  • Не надо использовать поля объекта для передачи параметров в фукции, для этого есть… параметры функций.
  • Тоже самое с результатом работы метода (за исключением редких случаев).
  • Поля объекта удобно использовать для настройки объекта, которая задаётся в конструкторе и не меняется на протяжении всей жизни объекта (либо меняется в очень понятных точках).
  • Поля лучше делать private, от греха подальше (область которую надо держать в голове сразу сокращается до одного класса), аналогигчно, public заставляет помнить про всех пользователей класса.

Глобальные переменные. Тут вообще в голове надо держать всё глобально – всё, что может писать в переменную. Да ещё и экземпляр всего один. Реально они нужны довольно редко. Кстати, на заметку, Singleton == глобальная переменная (почти). И тоже нужен крайне редко (инъекция зависимостей – наше всё).

Маленькое итого. Надо стараться по максимуму использовать передачу данных функциям. Параметры – значит параметры функций, результат – значит результат. Параметры должны быть явными (по максимуму типизированными) и понятными, что не понятно компилятору/парсеру должно проверяться на входе. После этого можно выбросить из головы происхождение параметров и писать только логику функции.

Поля объекта – для настройки объекта (вообще говоря, для состояния объекта, но состояние должно поминимуму включать временные переменные). И private, чтобы меньше заботиться.

Мы ограниченные, и это нормально

Posted by DoK | Posted in Uncategorized | Posted on 08-02-2010

Программист – существо ограниченное, я например – точно:

  • Я не могу держать в голове более 7 +/- 2 вещей.
  • У меня хорошая память, но я не могу запомнить все переменные, выражения, методы и классы в программе.
  • Также я не могу помнить все зависимости в коде.
  • Я плохо читаю запутанный код (и не хочу этого делать).

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

Нужно всего то:

  • Сократить объём вещей в памяти до необходимого количества ( = сделать остальное ненужным для решения конкретной задачи).
  • Использовать средства, готовые в любой момент заменить мою дырявую память (IDE), использовать максимальное количество проверок (компилятор, тесты), а также делать названия настолько очевидными, чтобы я не помнил название, а правильно его составлял каждый раз.
  • Максимально упрощать зависимости = делать их очень явными и локальными.

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

Что интересно, для того чтобы программировать быстро и качественно необходимо всего лишь победить эти проблемы, т.е. программировать понятно, а это даст всё остальное.

Разработка

Posted by DoK | Posted in Uncategorized | Posted on 08-02-2010

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

Заметка первая, зачем всё это нужно.

Мы никогда ничего не пишем только чтобы написать. Пишем мы, чтобы оно работало, развивалось, цвело и пахло. Ещё нельзя исключать такую вещь, как итеративность. Как бы нам ни хотелось, реалии таковы, что задачи не ставятся целиком, что сделали и всё. Нет, мы делаем макет, потом делаем пробную версию, потом делаем рабочую версию, потом делаем рабочую рабочую версию… ну вы поняли, и так раз 5, 10, 20, 100, в зависимости от длины жизни проекта (что самое забавное – чем живучее проект, тем больше раз мы будем в нём что-то менять/дорабатывать).

Ах да, ещё есть такая проблема, что всё надо делать а) быстро и б) хорошо. Казалось бы несовместимо. Совместимо. Причём иногда оказывается, что быстро и плохо сделать не получается. Парадокс.

Я отвлёкся. Суть такая, что основная задача – быстро писать качественный код, при чём такой, который потом можно будет также быстро дорабатывать, менять, убирать и т.п. Важно – чтобы потом можно было не выкинуть всё и начать заново, а именно использовать максимум наработок.

Собственно об этом всё дальше и будет.

ОО программирование, как я его сейчас понимаю

Posted by DoK | Posted in ООП | Posted on 19-05-2009

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

Итак, что было

Нас учили, что основные понятия ООП, это:

  • Инкапсуляция
  • Наследование
  • Полиморфизм

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

То, что я извлёк (это вероятно относится и ко мне и к программе, т.к. очень многие после той же программы думают также) – это то, что я могу написать в резюме «я знаю ООП», что в итоге сводится к тому, что я умею записывать процедурный код в классы, и моим любимыми паттернами являются недоделанный Template Method и God Object (не в крайнем проявлении, но как направление).

Что надо было вбить или услышать

Инкапсуляция - это не только то, что можно сокрыть часть данных и методов, но сам принцип, что мы ограничиваем внешнее знание об объекте с целью уменьшения количества зависимостей. А также важны примеры, как это делать или не делать… без фанатизма «все свойства должны быть закрытыми», но с пониманием того, что надо задумываться «а по сути это ожидается снаружи?».

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

Наследование - о да! Наследование решает все проблемы :) . И на следующем шаге порождает в два раза больше, если использовать неправильно. Особенно прикольны рассуждения «этот метод нужен в 3 из 5 наследников, давайте поднимем его в родителя». Вообще наверное корень зла в том, что наследование у меня в своё время спозиционировалось, как способ повторного использования кода, хотя таковым не является. Наследования – это отношение «является» и очень сильная связь между двумя классами. Если нужно повторное использование кода – есть масса других механизмов.

Мой любимый Template Method хотя и имеет право на жизнь в принципе, но в тех объёмах, в которых он использовался (и используется порой :-Р) – страшное зло, потому что исполнение неявно скачет из одного класса в другой и когда что произойдёт предсказать сложно (как и что обрушится, если что-то изменить). (З.Ы. Там надо было его ограничивать и делать более строгим, никаких реализаций по умолчанию, которые потом опционально перекрываются).

Ещё про наследование – очень важная вещь – принцип подстановки Лискоу. Что объект порождённого класса должен использоваться везде где используется его предок без нарушения интерфейса (в частном) и вообще обещаний (в целом). Является ли правильной цепочка наследования Фигура – Прямоугольник – Квадрат? В общем нет, потому что с точки зрения программирования Квадрат часто не является Прямоугольником (как «честно» реализовать методы setWidth/setHeight в квадрате?).

Полиморфизм – вот тут интересно, пусть это моё имхо, но полиморфизм надо рассказывать на базе интерфейсов (каждый класс – неявно порождает интерфейс, который наследуют предки, так что для классов и наследников всё верно).  И это тоже к принципу подстановки Лискоу – кто реализует интерфейс – реализует его полностью и соблюдая обещания, если нельзя полностью – то что-то не так, возможно нужно два интерфейса вместо одного.

А сюда кстати завязана инкапсуляция с другого конца – каждый код должен очень чётко и минимально сформулировать, что ему надо от других объектов, и больше ничего не спрашивать. Именно поэтому я считаю, что интерфейс должен предоставляться «потребителем» объекта. Т.е. если есть алгоритм сортировки, то он даёт интерфейс «Сортируемое» – и уже задача пользователя реализовать его или адаптер, если они хотят сортироваться этим алгоримом. Можно пойти и дальше, отвязав для нескольких потребителей общий интерфейс, важно, не столкнуться с ситуацией, когда интерфейс распухает, а каждый потребитель использует только часть – тогда нужны разные интерфейсы. // Лирическое отступление :)

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

З.Ы. повторное использование. Написал выше, что без наследования, а как иначе? Мы создали объект, вызвали метод – вот мы и использовали код повторно. Надо поменять? Сделаем, чтобы метод (или конструктор объекта, это зависит от ситуации) принимал другой объект, реализующий меняющуюся часть – вот у нас появился «набор» методов – для каждого нового объекта это новый метод (это и есть полиморфизм). Код использовался повторно, а мы не обременили себя жёсткой связью «наследуется», код гибкий.

Что ещё

А ещё очень интересно расширить применение и показать альтернативы. Расширить применение – показать, что такое TDD, объяснить, зачем и дать попробовать… ещё при обучении. Это классная лакмусовая бумажка, показывающая закрепощённость и неправильность ОО-кода.

Альтернативы – мне очень понравилось познакомиться с миром функционального программирования, не могу сказать, что я по нему спец, но это однозначно добавило новых подходов в моё программирование. Об этом тоже надо рассказать.

Функциональное программирование – всё функция. В применении к вебу страница – это функция от GET, POST, FILES, COOKIES. Функция не имеет состояния – т.е. результат всегда зависит только от параметров, и это один из полезных принципов. Функция может быть суперпозицией других функций, функции могут передаваться как параметры, может быть замыкание – функция, принимающая в качестве параметров часть того места, где она быа создана (не знаю, правильно ли это теоретически, там carrying и всё такое, но по сути так).

Что это даёт в моём ООП? :) То, что в объектах не должно быть состояния помимо их настройки, да, настройка может меняться, но при этом вся динамика – отдельно. Пример. Есть класс, обрабатывающий ввод данных, должен ли этот класс сам хранить введённые данные и ошибки ввода? Нет, они не имеют к нему отношение – они являются результатом проверки. Он хранит настройку «как проверять», её можно менять по ходу, но он не хранит сами результаты, желательно даже во время обработки.

Ещё одна штука – связь вызовов методов. Имхо желательно минимизировать зависимости «вызови метод А, получи результат, добавь и вызови метод Б», и не дай бог метод А что-то меняет внутри объекта и без него метод Б вызвать нельзя это совсем ужас… Да и вообще, начинал писать абзац к тому, что очень многие вещи решаются паттерном «Команда», что является ничем иным, как замыканием в функциональном программировании – это объект, который можно настроить при создании, а дальше у него есть только один метод с определёнными параметрами… такой аналог функции-параметра.

Итого – сейчас очень тянет к архитектуре, где объекты хранят только то, что определяет их работу, но не промежуточные данные, сами объекты маленькие, фактически близки к функции с несколькими предзаданными параметрами… возможно к семейству функций с одинаковыми настройками. А результат работы программы – некий прогон входных данных через сеть связанных объектов. Сама сеть от этого меняется только если входные данные заставляют её измениться, все же временные эффекты после выполнения забываются. И этот подход транслируется на каждый отдельный кусочек выполнения.

Отпуск (Крым, май 2009)

Posted by DoK | Posted in Крым | Posted on 12-05-2009

В двух словах, как это было – было здорово. Мы сходили в поход в районе Демерджи.

С погодой повезло не особенно – первую часть путешествия был мелкий дождик (а стоянка в районе агнарского перевала так и вообще проходила в облаке – тот же мелкий дождь + сильный туман). Потом всё более-менее раздуло и облачка чередовались с чистым небом.

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

Вторая часть отдыха проходила в Коктебели… тут можно много рассказать – отличная еда – солёности, местная (мясная!) колбаса, вкуснейшие кисломолочные продукты (такой сметаны я ещё не ел)… и конечно же вина. Дегустация в Солнечной Долине, портвейн 90-го года, и куча куча всего другого. Жаль, в моём городе нет фирменного магазина, с ним жизнь была бы веселее :) ).

Вот. В общем пора работать, а рассказывать можно очень долго.

З.Ы. едва не забыл самое важное! Самое классное в этом походе – это компания, «старейшие» члены которой ходят в эти места едва ли не 20 лет. Именно компания сделала интересный поход по интересным местам действительно офигенным и незабываемым.

Пара идей по отладке

Posted by DoK | Posted in Uncategorized | Posted on 05-02-2009

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

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

Что такое отладка?

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

Как сужать контекст ошибки? В целом это всегда уменьшение размера кода, в котором потенциально находится ошибка, а также условий, значений ввода и т.п. Т.е. пришёл менеджер «ничего не работает» – контекст – весь код. Что не работает? Такая-то страница. Что там? Форма не сохраняется. А что вводили? …

Эта часть очевидна, я просто хочу подчеркнуть, что это всего навсего сужение контекста ошибки. Дальше процесс точно такой же – формулируем некоторую гипотезу «на входе в скрипт данные правильны» – смотрим данные $_POST, ага, всё, про ошибки в хтмл можно забыть, это не там. Или наоброт. Т.е. дальше процесс идёт также, последовательным уменьшением контекста ошибки, пока она не станет ясна.

Что может помочь?

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

  • Расстановка var_dump-ов будет очень неэффективной (хотя приемлимой для начала). 
  • Могут помочь различные встроенные трассировки (например, у нас есть режимы, когда по параметру в адресе хорошим людям выводятся все SQL запросы, все удалённые ошибки и т.п.). Это позволяет проконтролировать несколько точек сразу, даже не касаясь кода.
  • Не забываем про firebug – это очень хорошее средство контроля вывода, ввода (можно смотреть что постилось, какие куки, какие заголовки) и взаимодействия – вкладка «net». 
  • Наконец, последнее по порядку не по значению, интерактивная отладка на сервере (пусть даже локальном). Интерактивным отладчиком однозначно стоит овладеть, он позволяет смотреть все переменные, проходить о всем вызовам и т.д., т.е. если сравнивать с вардампом – это возможность посмотреть вардамп любой желаемой переменной на любой строке без перезагрузки страницы и правки кода. Это очень быстрое средство при отладке средних и сложных ошибок.

А ещё?

  • Ещё надо поменьше доверять и побольше проверять. Я не раз попадал на часовую отладку ошибки в рабочем коде просто потому, что подумал «я знаю, ошибки в этом месте нет, надо смотреть там» – подумал и не проверил, на самом ли деле нет ошибки. Просто надо держать глаза открытыми.
  • А ещё при отладке, как это ни странно, помогает грамотно написанный код. Как заметил один мой умный знакомый, нет режима «убирания ошибок», есть режим «скрытия ошибок», но они никуда не уходят. Т.е. приложение должно быть написано так, чтобы максимально сообщать о том, что что-то пошло не так, сразу же, как пошло не так. Очень хороший материал – МакКоннелл, «Совершенный код», глава про защитное программирование.

Ошибку нашли, что дальше?

Дальше её надо исправлять. Здесь тоже пара моментов:

  • Если ошибка сложная, очень желательно её не заткнуть, а понять суть проблемы (для сложных ошибок обычно надо искать проблему в понимании логики приложения/предметной области). Такое исправление будет более сложным, зато снимет проблемы в будущем.
  • Модульные, браузерные и прочие тесты. Если они есть – исправлять сложные ошибки, переписывать большие куски и т.п. – удовольствие – код становится лучше и ты знаешь, что можешь проверить, чтобы ничего не сломать.
  • Подобные ошибки. Исправив одну ошибку, часто даже уже коммитя её, я задаю себе вопрос «а где может быть такая же ошибка?»… и исправляю ещё пару мест, которые вылезли бы с такими же страданиями через неделю-две.

Учимся или стоим на месте?

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

create_function?

Posted by DoK | Posted in Uncategorized | Posted on 05-02-2009

Я долгое время говорил, что eval is evil, ругался на ребят, которые используют create_function (это по сути дела eval). А тут из соображений практичности подумал, м.б. ну их нафиг эти принципы?

Существует много мест, где надо передать небольшой коллбэк для того, чтобы как-то кастомизировать какую-то часть, причём часто это две строчки. Когда будет PHP 5.3 – будут closure, и вообще говоря часто важны не столько closure сколько возможность удобно объявить инлайн функцию (там даже не обязателен доступ к внешним переменным). А сейчас?

А сейчас «официальный» путь, против которого я ругался – create_function. Я тут заценил а долог ли он? На моём не-серверном компе получилось 0,025 мс для маленькой функции. Тут я и подумал – ну их эти предрассудки. Попробовать что ли их поюзать?

З.Ы. Никто не отменял здравую логику, как то «не плоди create_function-ы в больших циклах» и «не подставляй в их текст переменные, пока не припрёт, а когда припрёт – всё равно не подставляй».

Есть комментарии?

Конструкторы, поддержание состояния объекта

Posted by DoK | Posted in PHP, ООП | Posted on 13-01-2009

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

Что такое интерфейс класса? Набор некоторых обещаний. В общепонимаемом смысле это набор методов и их сигнатур. Вообще говоря можно также расширить это определение на атрибуты (обещать, что есть такой то атрибут который значит то-то). Но по сути интерфейс – это набор некоторых обещаний. К сожалению, большинство языков могут контролировать их достаточно примитивно. Т.е. просто, метод, сигнатура… А что этот метод должен выполнять смысл, задаваемый интерфейсом – нет.

Всё это к тому, что помимо формального интерфейса класса есть ещё логический интерфейс, который собственно и нужен программисту для понимания как же работать с классом. Может ли этот логический интерфейс соответствовать формальному? Можно его сильно приблизить. Что формально не задаётся? Комментарии и смысл в них.

Часто можно встретить комментарии «перед использованием объекта вызовите метод initialize()». К сожалению, глядя на список методов класса сложно вычислить эту зависимость. Это приводит к ошибкам/сложностям в разработке.

Фактически, имхо, надо минимизировать последовательности «метод А нельзя вызывать, пока не вызван метод Б», или «сначала надо задать свойство А, потом вызвать метод Б» и т.д. Т.к. они не очевидны.

Пример (можно встретить почти в любом туториале по ORM):

$post = new Post();
$post->author = $someAuthor;
$post->text = $someText;
$post->tags = array($tag1, $tag2);
$post->save();

Что тут не так? То, что в моменты после создания и до сохранения объект существует, но пользоваться им нельзя, т.к. он ещё неполноценен. Какой тогда смысл конструктора, если он даёт нам неполноценный объект? М.б. правильнее было бы:

$post = new Post($someAuthor, $someText, array($tag1, $tag2));
$post->save();

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

В пхп, к сожалению, ввиду отсутствия именованных аргументов функций есть некоторые сложности с чтением такого кода :-/ и модификации списка полей.

Тоже самое касается валидации изменений. Часто надо заполнить N полей (не важно, через set или через свойства), и только при попытке save нам скажут, что дескать были не правы. Я понимаю, что высказываю очень радикальную точку зрения. Но скажем, если у нас задаётся две даты, дата заезда и дата выезда из отеля, то мы легко можем обновить такой объект в противоречивую ситуацию, когда выезжать придётся раньше заезда, и контроль будет в лучшем случае при сохранении.

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

Всё идёт к тому, что надо проверять данные прямо при установке – радикальная точка зрения :) .

А если надо передвинуть даты заезда/выезда? Самим анализировать даты и изменять сначала одну, потом другую? Бред. Тогда можно сделать метод измениДаты(датаЗаезда, датаВыезда). Соответственно где-то здесь есть некоторый ключик – мы делаем несколько изменений за раз и только потом проверяем состояние.

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

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

Причём это может быть прозрачно для пользователя:

$booking = new Booking(); // Если не задали всех данных - объект в "неправильном состоянии"
$booking->checkIn = strtotime('+15 days');
$booking->getDays(); // Посмотрит на флажок, решит, что есть изменения и проверит
    // состояние объекта. Ругнётся, т.к. checkOut не задан
$booking->checkOut = strtotime('+4 days', $booking->checkIn);
$booking->getDays(); // Опять же проверит, скажет, что объект согласован

$booking->save(); // Также выставит согласованность

Такие вот раздумья. Совсем «правильный» вариант, как всегда – правильный, но заставляет писать много кода, и местами его дублировать (согласованность дат в примере надо проверять методах изменения каждой и в методе изменения разом).

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