UP. Первая итерация
Вот прошла неделя и с небольшой задержкой, вызванной отсутствием времени, выкладываю результаты первой итерации.
Что сделано:
- Описаны прецеденты на эту итерацию
- Реализованы диаграммы классов и последовательностей
- Создана базовая файловая архитектура
- Реализованы задачи
- Сделаны некоторые открытия в области ООА/ООП
Но обо всем по порядку
Осторожно - много текста!!!
Описание прецедентов
Собственно задача “описать требования прецедентов” является самой сложной и самой важной в рамках проектирования системы. Нередко допущенные на этой стадии ошибки приводят к плачевным результатам и затягивают сроки из-за изменения требований.
Прецедент - это некоторая история, написанная для участников (actors) системы. Прецедент описывает пути достижения целей, а так же рассматривает все альтернативные сценарии развития - будь то ошибки по вине участников, различное поведение в зависимости от некоторых факторов или банальные ошибки в функционировании ПО.
Описание прецедента может быть кратким, а может быть формальным - все зависит от размера задачи или проекта. Краткое описание должно содержать основополагающие моменты и быть понятным для постороннего разработчика. Полное же описание, требует выдерживать некоторые правила в оформлении. Оно строится по следующему принципу:
Вначале перечисляются участники. Это могут быть как различные пользователи (администраторы, покупатели, менеджеры), так и сама система. Если прецедент не сложный, а участников не более двух-трех, то опустить эту часть можно, так как роли будут легко восприниматься из контекста описания.
Вторая важная часть - предусловия. Некоторые состояния и события всей системы, которые должны быть выполнены, что бы прецедент мог выполняться. В нашем случае, при рассмотрении обоих выбранных прецедентов предусловиями является авторизованность пользователя в системе.
Следующим в списке является описание триггера - события, которое инициирует начало выполнения сценария. Если рассматривать прецедент “создание счета”, то описать триггер можно как “Пользователь находится на странице Счета::новый”, либо “Пользователь переходит на страницу Счета::новый”.
Далее мы описываем успешный сценарий - последовательность действий, которые приводят к достижению поставленных целей. Необходимо выбрать уровень детализации таким образом, что бы сценарий был информативным и не выглядел глупым. Таким образом заранее стоит отбросить попытки создать описаняи вида:
- Пользователь нажал на кнопку “добавить”
- Система добавила счет пользователю
Такое описание не дает нам дополнительного объяснения как достигалась цель и, соответственно, не несет никакой пользы. Вторая крайность - слишком детальное описание сценария:
- Пользователь открыл страницу Счета::новый
- Пользователь указал название
- Пользователь указал тип валюты
- Пользователь указал остаток на счете
- Пользователь навел указатель мыши на кнопку “отправить”
- Пользователь нажал на кнопку “отправить”
- Система получила запрос с данными
- Система нашла необходимый контроллер
- …
Нет. Такое решение нам тоже не подходит, так как оно отвлекает от основной идеи и только запутывает в поиске правильного пути. Да и для детального описания какой-либо части мы можем использовать перекрестные ссылки, не отвлекаясь атким образом на детяли в настоящее время.
После описания основного сценария, следует задуматься о сценариях альтернативных. Для их описания необходимо рассмотреть каждый пункт основного сценария и найти в них возможные пути ветвления. Начинать альтернативный сценарий стоит с цифро-буквенного кода, который указывает на пункт основного сценария, для которого пишем альтернативный, а так же позволяет создавать несколько альтернативных сценариев для одного основного пункта. Так код “4a” подразумевает, что это первое ветвление для шага номер 4 в основном сценарии, а код “4б”, говорит о том, что это второй вариант исхода событий. Альтернативные сценарии могут так же начинаться со звездочик “*” - это будет означать, что такой ход событий может возникнуть в любом месте основного сценария. Надеюсь объяснил понятно, хоть и запутанно
Далее дополняем описание постусловиями - тем, что должно быть достигнуто в случае удачного исхода событий, к чему нам надо стремиться при реализации.
Другими возможными деталями описания могут быть “Специальные условия” (перечисление возможных специфических требований) и некоторые другие дополнения.
Диаграммы
Как только с описанием закончили, стоит его перечитать и, возможно, дополнить или… переписать заново
Не стоит забывать что описания - основная наша цель! и уже потом - все остальное: диаграммы, код, структура баз данных.
Когда же описание подогнано под необходимый уровень детализации и содержит все необходимые нам сведения, стоит рассмотреть аспекты, которые могут быть непонятными. Очень тяжело в описании продемонстрировать связи различных объектов и участников системы, поэтому часто стоит построить диаграммы классов.
Диаграмма классов
Диаграмма классов - статическая диаграмма, иллюстрирующая связи между объектами, архитектурные решения, акцентирующая внимание на свойствах и методах объектов. Это первый шаг к реализации системы.
Посмотрим на пример для нашего прецедента “создание счета”. Даже без знания UML можно понять эту диаграмму: вот есть некий Пользователь (User), у пользователя может быть от 0 до бесконечности Счетов (Account), каждый счет может принадлежать к какому-то типу (кредит, займ, кредитная карта, наличные, депозит) и каждый из них имеет методы save() и getPropertiesForm(). Диаграмма вроде бы понятна. О том, где мы взяли методы еще поговорим, а пока рассмотрим что обозначают непонятные символы на диаграмме.
Прямая с черным ромбом - агрегация. Просто прямая обозначает какое-то отношение между объектами. Прямая с ромбом, означает агрегацию. Тоесть в объекте, на который указывает ромб, может находится некоторое кол-во объектов из другого конца прямой. При этом данный тип отношения подразумевает, что сами по себе агрегируемые объекты существовать не могут.
Прямая с пустой стрелочкой - обобщение (уточнение). Указывает на то, что класс, на который указывает стрелочка более общий, а другой класс - более конкретный. Такую же прямую с пустой стрелочкой можно провести от “мыши” или “трекбола” к “манипулятору” - мышь и трекбол - более конкретные классы по отношению к манипулятору.
Диаграмма последовательностей
Второй тип даиграммы, который стоит рассмотреть - диаграмма последовательностей. Этот тип диаграмм используется для демонстрациивзаимодействия между классами/объектами/участниками системы.
Диаграмма последовательностей близка к описанию конкретных методов классов.
Что может поведать наша диаграмма? Во-первых следует помнить, что диаграмма читается сверху вниз, если не указан иной порядок (обычно указывается цифрами возмле вызовов методов). Прямоугольные желтые области - классы, либо экземпляры классов, либо участники системы, а вертикальные пунктирные линии, отходящие от них - линии “жизни”. Каждая стрелка - обращение к методу класса, на который она указывает. Так самое первое действие можно прочитать так: Пользователь (заметьте, что не класс, а участник) вызывает метод newAction() класса AccountsController (тоесть AccountsController обладает методом newAction ). Таким описанием мы подразумеваем, что вызов из браузера некоей страницы, приведет к обращению к данному методу данного контроллера. Этот пример очень приближен к нашей области применения, так как архитектура будет построена на ZF с использованием подхода ZendMVC.
Так же стоит учитывать, что не все указания на диаграмме стоит воспринимать буквально. Так довольно часто можно встретить вызовы методов new() или create(), но они зачастую означают инстанциирование экземпляров классов и последующий вызов конструкторов.
Пунктирные линии с надписями “rendered content” - это указание на возвращение к инициатору событий, а в нашем случае означает рендеринг страницы в браузере пользователя.
Думаю что прочитать диаграмму вам стоит самостоятельно, а если возникнт вопросы - обсудим их в коментариях
З.Ы. Кстати если интересно почему какой-то конкретный класс порождает другие классы, то пишите в коментарии или на почту и я все объясню более детально.
Базовая файловая архитектура
В репозитории проекта уже можно забрать некоторое кол-во кода, на основании проектного решения. А если проследить историю изменений, то можно найти десятую ревизию с комментарием: “созданы основные файлы/каталоги и проведена базовая настройка окружения”. Что же было проделано на этом этапе? На этом этапе были созданы предусловия для использования ZendMVC: создан .htaccess с необходимыми правилами mod_rewrite, создана точка входа по шаблону FrontController (index.php); был создан файл первичной конфигурации config.ini-sample (который используется как шаблон для файла config.ini, который добавлен в список исключений (svn:ignore в значение “*.ini”) для каталога includes/, что бы мы могли сделать настройки специфическими для каждого из серверов разработки). Так же был создан файл вторичной конфигурации на основе config.ini - bootstrap.php. Он создает необходимые для работы ресурсы, настраивает переменные окружения и подготавливает для работы необходимые классы. К нему же подключены файлы constants.php и functions.php, которые, соответствуя своим названиям, содержат общесистемные константы и некоторый набор функций соответственно. Последними шагами было создание контроллера IndexController с дейтсвием indexAction и соответствующего ему представлению scripts/index/index.phtml. Это все, что необходимо для первого запуска нашего приложения в браузере.
Другим, заслуживающим отдельного абзаца моментом, является подключение внешних библиотек: Doctrine и ZendFramework. Можно было бы конечно их скачать архивчиком и расположить в созданной для них папочке libs/, но я как любитель всего самого свежего и свежепоправленного, то эти библиотеки мы подключаем таким образом, что бы они всегда были в актуальном состоянии. Благо SVN предоставляет такую возможность на базовом уровне - это совйство “svn:externals”, указав такое свойство через svn propset мы добъемся того, что в наш проект будет подтягиваться последняя версия из SVN’ов Doctrine и ZendFramework. Но с плюсами от работы с последней версией, не стоит забывать и о том, что иногда недобросовестные разработчики могут закоммитить некошерный код - и тогда бывает бяка…
Реализация проектного решения
Следуюший шаг - написание кода. Если честно, то его можно сгенерировать из наших диаграмм (BOUML хорошо с этим справляется), но из-за того, что мы будем использовать внешние фреймворк и ОРМ, то такой надобности я не нахожу и код пишу весь с ноля, стараясь не заыбвать о комментировании
Если вы сделаете себе checkout проекта и заглянете в можели и контроллеры, то обнаружите, что единственным отклонением от нарисованных диаграмм ялвляется описание геттеров и сеттеров (get*, set* методы), а так же специфические вещи типа инстанциирование журналов (logs), описание структуры БД в моделях (требование Dcotrine ORM) и прочие несущественные мелочи, которые понятны из контекста. Все же остальное полностью следует предоставленным диаграммам и выполняет сценарии описанные в прецедентах.
Думаю, что лучше один раз увидеть код, чем сто раз прочитать его описание тут, а если возникнут вопросы - их можно будет обсудить в коментариях, или дополнитеольным топиком, который рассмотрит детали реализации.
Кстита для того, что бы запустить пример, необходимо всего несколько действий:
- Сделать checkout проекта
- Скопировать файл config.ini-sample в config.ini
- Отредактировать config.ini
- Проверить права на каталог /logs (php cli и apache должны уметь в него писать)
- Запустить файл /scripts/setup.php
Все. Создание БД и таблиц пройдет в автоматическом режиме, а проект будет доступен из браузера как только вы создадите в Apache новый VirtualHost, указывающий своим DocumentRoot на каталог html.
Заключительная часть
Заключительным шагом итерации является определение требований на следующую итерацию. Руководствуемся теми же принципами что и раньше - выбираем либо самую сложную, либо самую подверженную изменениям часть системы, либо часть, которая тесно связана с уже реализованными частями. В данном случае считаю что таковой может быть прецедент “создание чека” - им и займемся на этой неделе
ПыСы
Пока готовил свои первые в жизни реальные диаграммы, многое показалось сложным, так как сильно отличалось от теории, посему приходилось обращаться в гугл за помощью. Но, хочу заметить, что просидев около 4х часов для моделирования первых диаграмм, на все последующие трачу не более 5-7 минут.
А еще в дебрях интернета я наткнулся на отличный ресурс uml2, который в формате форума может помочь решить множество вопросов для начинающих в области ООА/ООП.
Ноябрь 7th, 2008 at 20:20
Замечательно вы описали результаты итерации ! И главное лаконично и понятно! Даже вопросов не возникает ! Спасибо!
Ноябрь 9th, 2008 at 01:37
Привет. Во-первых, спасибо за столь интересное и детальное описание процесса разработки. Во-вторых, желаю удачи и воли довести дело до конца
В третьих, интересует - собираешься ли использовать юнит-тесты, функциональные тесты или какой-то другой вид автоматического тестирования? Каким образом вообще будет проходить контроль качества?
Ноябрь 9th, 2008 at 16:52
Ух.. Я б юниттесты с удовольствием писал бы, но времени едва хватает на эти статьи и реализацию…
Вообще UP подразумевает покрытие юниттестами написанный код за несколько дней до окончания итерации - вместе с предоставлением готового кода на суд пользователей
Ноябрь 14th, 2008 at 17:55
Добавлю Вам в копилку еще и от себя благодарность.
Всегда не хватало учителя, наставника. А самоучение не всегда приводит к правильным результатам и в итоге иногда чувствую, что дальше развиваться не знаю в каком направлении, хотя знаний вроде хватает.
Очень приятно и доступно написано. И главное - как для меня, то здесь есть именно те основы (и не только), которые так необходимы.
С увлечением слежу
Терпения Вам в этом деле.
Ноябрь 28th, 2008 at 23:03
Про итерацию не совсем понял ,а в целом понравилось
Декабрь 17th, 2008 at 16:28
Много мыслей по поводу самого подхода, пожалуй слишком много, поэтому не буду пытаться из воспроизвести. Насчет реализации.
На мой взгляд хорошо структурированное (в контексте веб программирования с использованием ЗендФреймворка) ооп приложение должно быть именн объектно (сущностно что ле) ориентированно, чем в вашем коде. Может быть это у вас промежуточный вариант и вы собираетесь как это это всё поменять после какой нибудь итерации с рефакторингом, однако если нет… то - мое мнение, что работать с таблицами бд напрямую из контроллеров - это не совсем хорошо. То есть на грубом примере - есть сущность пользователь. Есть соотвествующая модель/класс/объект User. Мы работаем именно с это сущностью используя её интерфейс.
Делать “$user = Doctrine::getTable( ‘User’ )->find( 1 );” - не тру. Я совсем мало и мимолетом работал с доктрин (крутая штука, но я все равно использую встроенный ОРМ, лучше уж попарится и дописать эти нестед сетс и прочие вкусности, на мой взгляд), но мне кажется что можно и с доктрин все это дело более ООП-грамотно обставить.
То есь не писать $user = Doctrine::getTable( ‘User’ )->find( 1 ); а писать
$user = $users->getById($userId); где $users у нас child от абстрактного набор строк из таблицы а $user - child одной абстрактной строки из бд (это в том случае если нам нужны просто таблицы). таким образом мы получаем не массивы данных, а объекты (что не мешает нам сделать ->toArray() при необходимости).
Ещё - в зенд фреймворк везде приватные и протектед члены классов и методы начинаются с _ подчеркивания предлагаю придерживаться этого - хорошо когда все написано в одном стандарте (и ничто не заставляет вас их перемешивать)
Декабрь 18th, 2008 at 11:30
Во-первых - спасибо за развернутый комментарий
Теперь что касается ООП подхода. Конструкция “$user = Doctrine::getTable( ‘User’ )->find( 1 );” на самом деле ничего более чем “полноценный” ООП код по типу “$user = new Uer( 1 );”, но так как этот ОРМ не позволяет использовать такую структуру, то приходится использовать некоторые допущения. Нагромождать же поверх класса на основе ОРМ еще класс для “чистого” ООП я не решился, так как ни логики ни поведения это не меняет…
При подходе “$user = $users->getById($userId);” вобщем-то ничего не меняется, за исключением того, что конструкция на основе фасада: “Doctrine::getTable( ‘User’ )” заменена самописным враппером коллекции $users…
А насчет подчеркиваний (_), то они мне не нравятся как пережитки РНР4, где приватных и защищенных методов и свойств просто небыло…
Да и стандарт оформления кода у меня вобщем-то довольно иной чем у Зенд, а подстраиваться в проекте на стандарты одной из библиотек думаю неверно…
Декабрь 18th, 2008 at 12:02
>> “Во-первых - спасибо за развернутый комментарий”
Спасибо вам за интересный материал.
>> “При подходе “$user = $users->getById($userId);” вобщем-то ничего не меняется, за исключением того, что конструкция на основе фасада: “Doctrine::getTable( ‘User’ )” заменена самописным враппером коллекции $users…”
Как же не меняется? Пропадает объектная ориентированность, одна из главных прелестей её - возможность расширения класса Пользователей, его доработки. Например буквально на днях в левом проекте (не моём, я на подхвате и вообще не на ЗФ) я расширил класс пользователя в класс Персоны и Участники (суть их не важна, но они весьма специфичны) в каждый класс добавив функционал реализующий им свойственное поведение. При этом для хранения данных используется таже таблица что и для родителя-класса Пользователи (отличаются только группы пользователей). При этом у меня весь функционал по авторизации и прочим вещам которые можно было делать с пользователями остался и в этих объектах (так нужно было по задаче). Я понимаю что все это можно сделать и на доктрин, но это будет имхо совсем не ооп, а контроллеры “растолстеют”, что не есть гуд. Либо я чего то не понимаю и доктрин provides не меньшую гибкость?
Также, насчет того что
>> “Нагромождать же поверх класса на основе ОРМ еще класс для “чистого” ООП я не решился, так как ни логики ни поведения это не меняет…”
- я считаю что любое нагромождение стоит того чтобы быть нагроможденным, если оно делает более логичной, удобной для вас, вас в перспективе, и разработчиков которые, возможно будут с этим кодом работать, структуру приложения, архитектурную его суть. Конечно если в вашей модели объекту пользователя в принципе никогда не понадобится быть чем-то более чем массивом данных… такого ваше решение, как архитектора (и вполне возможно я просто недостаточно далеко смотрю и вы правы), но я хотел высказать своё мнение по этому поводу для тех, кто может быть будет читать эту статью и пытаться сделать что-то подобное на ЗФ.
>> “А насчет подчеркиваний (_), то они мне не нравятся как пережитки РНР4, где приватных и защищенных методов и свойств просто небыло…”
Хм. никогда не думал с этой точки зрения того что это пережиток. Часто попадаются фрагменты когда пишут $this->mApples (m_Apples) типа из си люди там где можно обращаться к члену класса без $this-> ну это бред полнейший конечно. Подходы типа $this->s_AppleName имеют право на жизнь, хотя конечно просто разработчик должен быть достаточно опытен чтобы грамотно распоряжатся нетипизированностью языка, тогда в типовых приставках нет никакого смысла.
Однако… по-моему опыту работы (в основном чтение исходников зф и кода написанно по такому же принципу, поиск багов и ответов на вопросы) подчеркивания в именах переменных существенно облегчают понимание ООП структуры и делают более прозрачным сам класс, его средства и цели. Но это конечно дело субъективное.
>> “Да и стандарт оформления кода у меня вобщем-то довольно иной чем у Зенд, а подстраиваться в проекте на стандарты одной из библиотек думаю неверно…”
А я как-то не заметил особо другой разницы, поэтому подумал что с подчеркиваниями это что-то вроде недосмотра =). Чтож, просмотрю еще раз ваш код и буду ждать продолжения в надежде найти интересные и удобные решения в разработке.
* чет у меня тут заглючило, поэтому если будут дубликаты комментариев - прошу прощения.
Январь 25th, 2009 at 04:33
Поддержу товарища толстые контроллеры, которые делают запросы к базе данных пусть даже через абстракцию не хорошо.
Такой код будет трудно поддерживать, просто получается уже не MVC а VC