Создание, анализ ирефакторинг
Скачать 3.16 Mb.
|
194public int getId() { return id; } 1 Приведенный пример можно упростить — существуют специальные механизмы, исполь- зующие правила конфигурации и аннотации Java 5 для сокращения объема явно опреде- ляемой «связующей» логики . 2 По материалам http://www .onjava .com/pub/a/onjava/2006/05/17/standardizing-with-ejb3- java-persistence-api .html . 194 Аспекты AspectJ 195 public void setId(int id) { this.id = id; } public void addAccount(Account account) { account.setBank(this); accounts.add(account); } public Collection return accounts; } public void setAccounts(Collection this.accounts = accounts; } } Этот вариант кода намного чище исходного кода EJB2 . Некоторые подробности о сущностях все еще присутствуют в аннотациях . Тем не менее, поскольку эта информация не выходит за пределы аннотаций, код остается чистым, понятным, а следовательно, простым в тестировании, сопровождении и т . д . Часть информации о сохранении объектов, содержащейся в аннотациях, можно при желании переместить в дескрипторы XML, оставив действительно чистый POJO-объект . Если детали сохранения объектов изменяются относительно ред- ко, многие группы отдадут предпочтение аннотациям, но с гораздо меньшими отрицательными последствиями по сравнению с EJB2 . Аспекты aspectJ Наконец, самым полнофункциональным инструментом для разделения областей ответственности посредством использования аспектов является язык AspectJ 1 — расширение Java, предоставляющее «полноценную» поддержку аспектов как модульных конструкций . Чистых Java-решений на базе Spring и JBoss достаточно для 80–90% ситуаций, в которых применяются аспекты . Тем не менее AspectJ предоставляет очень мощный и разносторонний инструментарий для реализа- ции разделения ответственности . Недостатком AspectJ является необходимость освоения нескольких новых инструментов, а также изучения новых языковых конструкций и идиом . Эти проблемы отчасти компенсируются появившейся недавно «аннотационной» формой AspectJ, в которой аннотации Java 5 используются для определения аспектов в «чистом» коде Java . Кроме того, Spring Framework также содержит ряд функций, существенно упрощающих внедрение аспектов на базе аннотаций в рабочих группах с ограниченным опытом применения AspectJ . 1 См . [AspectJ] и [Colyer] . 195 196 Глава 11 . Системы Полное описание AspectJ выходит за рамки книги . За дополнительной инфор- мацией обращайтесь к [AspectJ], [Colyer] и [Spring] . Испытание системной архитектуры Трудно переоценить потенциал разделения ответственности посредством аспект- ных решений . Если вы можете написать логику предметной области своего при- ложения в виде POJO-объектов, отделенных от любых архитектурных областей ответственности на кодовом уровне, то перед вами открывается возможность проведения полноценных испытаний вашей архитектуры . Вы сможете развивать ее от простого к сложному, как потребует ситуация, подбирая новые технологии по мере надобности . Не обязательно создавать Большой Изначальный Проект (BDUF, Big Design Up Front) 1 . Более того, это даже вредно, потому что BDUF снижает возможность адаптации к изменениям из-за нашего психологического нежелания расставаться с результатами уже затраченных усилий; кроме того, изначально принятые решения влияют на наши последующие представления об архитектуре . Архитекторы, занимающиеся строительством зданий, вынуждены работать по принципу BDUF, потому что они не могут вносить радикальные архитектурные изменения в наполовину возведенное физическое строение 2 . Программные про- дукты тоже обладают собственной физикой 3 , но радикальные изменения в них могут оказаться экономически оправданными — при условии, что в программном проекте эффективно реализовано разделение ответственности . Это означает, что мы можем начать программный проект с «простой до наи- вности», но лишенной жестких привязок архитектуры, быстро реализовать пожелания пользователей, а затем добавлять новую инфраструктуру по мере масштабирования . Некоторые из крупнейших мировых сайтов достигли высочай- ших показателей доступности и производительности, с применением сложного кэширования данных, безопасности, виртуализации и т . д ., и все это делается эффективно и гибко — и только потому, что на каждом уровне абстракции их архитектура оставалась простой и обладала минимальными привязками . Конечно, это не означает, что за проект нужно браться по принципу «как-нибудь по ходу разберемся» . Вы уже в определенной степени представляете себе общий масштаб, цели и график проекта, а также общую структуру итоговой системы . Однако при этом необходимо сохранить возможность «смены курса» в соответ- ствии с изменяющимися обстоятельствами . 1 Не путайте с полезной практикой упреждающего проектирования . BDUF — привычка проектировать заранее все без исключения, до написания какого-либо кода реализации . 2 Впрочем, даже после начала строительства идут серьезные итеративные исследования и обсуждения подробностей . 3 Выражение «физика программного продукта» впервые было использовано в [Kolence] . 196 Применяйте стандарты разумно, когда они приносят очевидную пользу 197 Ранняя архитектура EJB была всего лишь одним из многих API, которые отлича- лись излишней сложностью, нарушавшей принцип разделения ответственности . Впрочем, даже хорошо спроектированный API может оказаться «перебором» в конкретной ситуации, если его применение не объясняется реальной необходи- мостью . Хороший API должен исчезать из вида большую часть времени, чтобы большая часть творческих усилий группы расходовалась на реализацию пожела- ний пользователей . В противном случае архитектурные ограничения помешают оптимальной реализации интересов клиента . Подведем итог . Оптимальная архитектура системы состоит из модульных областей ответственно- сти, каждая из которых реализуется на базе POJO-объектов. Области интегриру- ются между собой при помощи аспектов или аналогичных средств, минимальным образом вмешивающихся в их работу. Такая архитектура может строиться на базе методологии разработки через тестирование, как и программный код. Оптимизация принятия решений Модульность и разделение ответственности позволяют децентрализовать управ- ление и принятие решений . В достаточно крупной системе, будь то город или программный проект, один человек не может принять все необходимые решения . Как известно, ответственные решения лучше всего поручить самому квалифи- цированному . Однако мы часто забываем, что принятие решений лучше всего откладывать до последнего момента . Дело не в лени или безответственности; просто это позволяет принять информированное решение с максимумом воз- можной информации . Преждевременное решение принимается на базе неполной информации . Принимая решение слишком рано, мы лишаемся всего полезного, что происходит на более поздних стадиях: обратной связи от клиентов, возмож- ности поразмышлять над текущим состоянием проекта и опыта применения решений из области реализации . Гибкость POJO-системы с модульными областями ответственности позволяет при- нимать оптимальные, своевременные решения на базе новейшей информации. Кроме того, она способствует снижению сложности таких решений. Применяйте стандарты разумно, когда они приносят очевидную пользу Строительство кажется настоящим чудом из-за темпов, которым возводятся новые здания (даже в разгар зимы), и из-за необычных архитектурных дизайнов, ставших возможными благодаря современным технологиям . Строительство стало 197 198 Глава 11 . Системы развитой областью промышленности с высокой оптимизацией частей, методов и стандартов, сформированных под давлением времени . Многие группы использовали архитектуру EJB2 только потому, что она считалась стандартом, даже если в их проектах хватило бы более легких и прямолинейных решений . Я видел группы, которые теряли голову от разрекламированных стан- дартов и забывали о своей главной задаче: реализовывать интересы клиента . Стандарты упрощают повторное использование идей и компонентов, привлече- ние людей с необходимым опытом, воплощение удачных идей и связывание ком- понентов. Тем не менее, процесс создания стандарта иногда занимает слишком много времени (а отрасль не стоит на месте), в результате чего стандарты теря- ют связь с реальными потребностями тех людей, которым они должны служить. Системам необходимы предметно- ориентированные языки В области строительства, как и в большинстве технических областей, сформи- ровался богатый язык со своим словарем, идиомами и паттернами 1 , позволяю- щими четко и лаконично передать важную информацию . В области разработки программного обеспечения в последнее время снова возобновился интерес к предметно-ориентированным языкам 2 (DSL, Domain-Specic Languages) — отдельным маленьким сценарным языкам или API стандартных языков, код которых читается как структурированная форма текста, написанного экспертом в данной предметной области . Хороший предметно-ориентированный язык сводит к минимуму «коммуникаци- онный разрыв» между концепцией предметной области и кодом, реализующим эту концепцию — по аналогии с тем, как гибкие методологии оптимизируют обмен информацией между группой и ключевыми участниками проекта . Реа- лизация логики предметной области на языке, используемом экспертом в этой области, снижает риск неверного представления предметной области в коде . Предметно-ориентированные языки, когда они используются эффективно, под- нимают уровень абстракции над программными идиомами и паттернами проек- тирования . Они позволяют разработчику выразить свои намерения на соответ- ствующем уровне абстракции . Предметно-ориентированные языки позволяют выразить в форме POJO-объектов все уровни абстракции и все предметные области приложения, от высокоуровне- вых политик до низкоуровневых технических подробностей. 1 Работа [Alexander] оказала особенно заметное влияние на сообщество разработчиков ПО . 2 Например, см . [DSL] . [JMock] — хороший пример Java API, создавшего свой предметно- ориентированный язык . 198 Литература 199 Заключение Чистым должен быть не только код, но и архитектура системы . Агрессивная, «все- проникающая» архитектура скрывает логику предметной области и снижает гиб- кость . Первое приводит к снижению качества: ошибкам проще спрятаться в коде, а разработчику труднее реализовать пожелания пользователей . Второе оборачи- вается снижением производительности, а также потерей всех преимуществ TDD . Намерения разработчика должны быть четко выражены на всех уровнях аб- стракции . Это произойдет только в том случае, если он создает POJO-объекты, и использует аспекты (или другие аналогичные механизмы) для неагрессивного воплощения других сторон реализации . Независимо от того, проектируете ли вы целую систему или ее отдельные модули, помните: используйте самое простое решение из всех возможных . литература [Alexander]: Christopher Alexander, A Timeless Way of Building, Oxford University Press, New York, 1979 . [AOSD]: Aspect-Oriented Software Development port, http://aosd .net [ASM]: ASM Home Page, http://asm .objectweb .org [AspectJ]: http://eclipse .org/aspectj [CGLIB]: Code Generation Library, http://cglib .sourceforge .net [Colyer]: Adrian Colyer, Andy Clement, George Hurley, Mathew Webster, Eclipse AspectJ, Person Education, Inc ., Upper Saddle River, NJ, 2005 . [DSL]: Domain-speci fic programming language, http://en .wikipedia .org/wiki/ Domain-speci fic_programming_language [Fowler]: Inversion of Control Containers and the Dependency Injection pattern, http://martinfowler .com/articles/injection .html [Goetz]: Brian Goetz, Java Theory and Practice: Decorating with Dynamic Proxies, http://www .ibm .com/developerworks/java/library/j-jtp08305 .html [Javassist]: Javassist Home Page, http://www .csg .is .titech .ac .jp/chiba/javassist [JBoss]: JBoss Home Page, http://jboss .org [JMock]: JMock — A Lightweight Mock Object Library for Java, http://jmock .org [Kolence]: Kenneth W . Kolence, Software physics and computer performance measurements, Proceedings of the ACM annual conference—Volume 2, Boston, Massachusetts, pp . 1024–1040, 1972 . [Spring]: The Spring Framework, http://www .springframework .org [Mezzaros07]: XUnit Patterns, Gerard Mezzaros, Addison-Wesley, 2007 . [GOF]: Design Patterns: Elements of Reusable Object Oriented Software, Gamma et al ., Addison-Wesley, 1996 . 199 Формирование архитектуры Джефф Лангр Четыре правила Разве не хотелось бы вам знать четыре простых правила, выполнение которых помогало бы повысить качество проектирования? Четыре правила, помогающих составить представление о важнейших особенностях структуры и архитектуры кода, упрощающих применение таких принципов, как SRP (принцип единой ответственности) и DIP (принцип обращения зависимостей)? Четыре правила, способствующих формированию хороших архитектур? Многие полагают, что четыре правила простой архитектуры [XPE]Кента Бека оказывают значительную помощь в проектировании программных продуктов . 12 200 Четыре правила 201 Согласно Кенту, архитектура может считаться «простой», если она: обеспечивает прохождение всех тестов, не содержит дублирующегося кода, выражает намерения программиста, использует минимальное количество классов и методов . Правила приведены в порядке их важности . Правило № 1: выполнение всех тестов Прежде всего система должна делать то, что задумано ее проектировщиком . Си- стема может быть отлично спланирована «на бумаге», но если не существует про- стого способа убедиться в том, что она действительно решает свои задачи, то ре- зультат выглядит сомнительно . Система, тщательно протестированная и прошедшая все тесты, контролируема . На первый взгляд утверждение кажется очевидным, но это весьма важно . Невоз- можно проверить работу системы, которая не является контролируемой, а непро- веренные системы не должны запускаться в эксплуатацию . К счастью, стремление к контролируемости системы ведет к архитектуре с ком- пактными узкоспециализированными классами . Все просто: классы, соответ- ствующие принципу SRP, проще тестировать . Чем больше тестов мы напишем, тем дальше продвинемся к простоте тестирования . Таким образом, обеспечение полной контролируемости системы помогает повысить качество проектирования . Жесткая привязка усложняет написание тестов . Таким образом, чем больше тестов мы пишем, тем интенсивнее используем такие принципы, как DIP, и та- кие инструменты, как внедрение зависимостей, интерфейсы и абстракции, для минимизации привязок . Как ни удивительно, выполнение простого и очевидного правила, гласящего, что для системы необходимо написать тесты и постоянно выполнять их, влияет на соответствие системы важнейшим критериям объектно-ориентированного программирования: устранению жестких привязок и повышению связности . Написание тестов улучшает архитектуру системы . Правила № 2–4: переработка кода Когда у вас появился полный набор тестов, можно заняться чисткой кода и клас- сов . Для этого код подвергается последовательной переработке (рефакторингу) . Мы добавляем несколько строк кода, делаем паузу и анализируем новую архи- тектуру . Не ухудшилась ли она по сравнению с предыдущим вариантом? Если ухудшилась, то мы чистим код и тестируем его, чтобы убедиться, что в нем ничего не испорчено . Наличие тестов избавляет от опасений, что чистка кода нарушит его работу! 201 202 Глава 12 . Формирование архитектуры В фазе переработки применяется абсолютно все, что вы знаете о качественном проектировании программных продуктов . В ход идут любые приемы: повышение связности, устранение жестких привязок, разделение ответственности, изоляция системных областей ответственности, сокращение объема функций и классов, выбор более содержательных имен и т . д . Также применяются три критерия простой архитектуры: устранение дубликатов, обеспечение выразительности и минимизация количества классов и методов . Отсутствие дублирования Дублирование — главный враг хорошо спроектированной системы . Его послед- ствия — лишняя работа, лишний риск и лишняя избыточная сложность . Дубли- рование проявляется во многих формах . Конечно, точное совпадение строк кода свидетельствует о дублировании . Похожие строки часто удается «причесать» так, чтобы сходство стало еще более очевидным; это упростит рефакторинг . Кроме того, дублирование может существовать и в других формах — таких, как дубли- рование реализации . Например, класс коллекции может содержать следующие методы: int size() {} boolean isEmpty() {} Методы могут иметь разные реализации . Допустим, метод isEmpty может исполь- зовать логический флаг, а size — счетчик элементов . Однако мы можем устранить дублирование, связав isEmpty с определением size : boolean isEmpty() { return 0 == size(); } Чтобы создать чистую систему, необходимо сознательно стремиться к устра- нению дубликатов, пусть даже всего в нескольких строках кода . Для примера рассмотрим следующий код: public void scaleToOneDimension( float desiredDimension, float imageDimension) { if (Math.abs(desiredDimension - imageDimension) < errorThreshold) return; float scalingFactor = desiredDimension / imageDimension; scalingFactor = (float)(Math.floor(scalingFactor * 100) * 0.01f); RenderedOp newImage = ImageUtilities.getScaledImage( image, scalingFactor, scalingFactor); image.dispose(); System.gc(); image = newImage; } public synchronized void rotate(int degrees) { RenderedOp newImage = ImageUtilities.getRotatedImage( image, degrees); 202 |