Иногда вам нужно поделиться дизайном со своими коллегами. Вы можете пройтись по нему по коду и объяснить, какие классы и интерфейсы вы создали, но есть более абстрактные модели, которые вы можете показать ему, чтобы он быстро понял картину.
Одним из таких инструментов является UML и, в частности, диаграммы классов. Хорошая диаграмма классов может показать отношения между дюжиной классов и, таким образом, проектирование всего небольшого компонента.
Однако я не сторонник разработки программного обеспечения до написания кода, поскольку этот код является окончательным документом проекта. Когда мы готовим UML-диаграммы, мы, в основном, находимся в стадии после того, как мы протестировали код и зафиксировали его. Цель — быстро поделиться информацией, а затем выбросить диаграмму, когда она не синхронизируется с кодом.
Как?
Один из подходов к созданию диаграмм такого рода состоит в том, чтобы один из членов дуэта парного программирования рисовал на бумаге карандашом и резиной . Бумага по-прежнему является лучшим выразительным средством, а карандаш позволяет легко редактировать. В некоторых случаях мы даже обновляем наш бумажный лист во время извлечения классов на этапе рефакторинга.
Даже когда вы рисуете Uml с помощью компьютерного инструмента, скучная часть рисует диаграмму . В конце концов, часть информации уже встроена в код; и мы уже знаем, что эта диаграмма будет недолгой, так как код развивается быстро.
В качестве подтверждения концепции я начал писать инструмент PHP UML Generator, который извлекает диаграмму классов Uml из графа объектов PHP , который ранее был создан на некоторой фабрике. Это базовый реверс-инжиниринг.
Существующие инструменты, такие как Doxygen, создают диаграммы такого рода, но начиная со статического анализа кода, а не с графика живых объектов. Это означает, что Umlntrospector будет сложнее интегрировать, так как вам нужно загрузить приложение, но я думаю, что это перевешивается двумя факторами:
- единственным источником истины является граф объектов : если аннотации устарели или отсутствуют, если у вас есть работающий набор тестов, граф объектов будет синхронизирован с текущим состоянием кода.
- Решение более простое : Reflection используется для извлечения информации вместо анализа комментариев и кода docblock.
Характеристики
Поскольку это подтверждение концепции, я постарался создать минимально жизнеспособный продукт:
- по умолчанию интроспектор создает UML-определения для Yuml.me , используемые для визуализации. Другие адаптеры могут быть добавлены.
- Интроспектор следит за композицией и наследованием до N уровней : классовые отношения важнее методов, которых также, вероятно, будет слишком много, чтобы их показать.
- В настоящее время интроспектор не различает объекты одного и того же класса (целью является диаграмма классов, но они могут представлять собой больше ассоциаций, названных по-разному).
- Он может игнорировать пространства имен библиотек, такие как Zend_ или Doctrine_.
- Он пропускает скалярные поля, поскольку они почти не предоставляют информации.
- Он учитывает имена базовых классов и пространства имен PHP 5.3.
Вывод
В этой статье я провожу с сообществом идею анализа графа живых объектов. Вот несколько очень маленьких примеров результатов, полученных для объектов PHPUnit и Doctrine 2 Entity Manager с этим кодом (не может работать автономно, он включен только для пояснения):
<?php require_once '/home/giorgio/code/pug/tests/bootstrap.php'; require_once '/home/giorgio/code/ddd-talk/bootstrap.php'; class UmlIntrospectorPHPUnitTest extends PHPUnit_Framework_TestCase { public function testGenerationOfyUMLCode() { $introspector = new UmlReflector\Introspector; $directives = new UmlReflector\Directives; $introspector->visualize($this, $directives); var_dump($directives->toString()); } public function testGenerationOfyUMLCodeOnAPHPUnitMatcher() { $introspector = new UmlReflector\Introspector; $directives = new UmlReflector\Directives; $introspector->visualize($this->equalTo(new stdClass), $directives); var_dump($directives->toString()); } public function testGenerationOfyUMLCodeOnDoctrine2EntityManager() { $introspector = new UmlReflector\Introspector; $directives = new UmlReflector\Directives; $em = Test\BaseTestCase::getEm(); $introspector->visualize($em, $directives); var_dump($directives->toString()); } }
Вот изображения, полученные путем помещения сгенерированных директив в yUML (в будущем я также приведу прямую ссылку, но есть ограничение длины):