Проектировка
Элегантность окупается всегда. В этой короткой фразе заключается глубокий смысл. Создание элегантного решения требует большего времени, но в последствии адаптировать его для других задач или исправить ошибки будет намного проще и быстрее, в противном случае, могут понадобиться дни, недели или месяцы усилий прежде того, как Вы увидите результаты (даже если никто их и не оценит). Элегантность не только позволяет вам проще создавать или отлаживать программу, но и проще ее понять и обслуживать в дальнейшем, что очень положительно сказывается на финансах. Этот способ требует некоторого опыта для понимания, потому что он не может быть применен для небольшого кусочка кода. И ограничьте различного рода "подгонятелей", они только замедляют работу (в последствии). Сначала пускай он работает, а затем сделаем его быстро работающим. Этот принцип верен, если Вы уверены, что этот кусочек кода действительно важен и именно он будет бутылочным горлышком в вашей системе. Не делайте сразу его полностью оптимизированным. Дайте системе сначала наивозможно простую модель. Затем, если нужные участки не достаточно быстры, то оптимизируйте их. Затем вы уже всегда будете понимать, действительно ли это узкое место или все-таки нет. Сохраните ваше время для более важных дел. Помни принцип "Разделяй и властвуй". Если разрешаемая проблема слишком запутанна, то попытайтесь представить, что эта основная операция должна быть составлена из "магических" кусочков, которые уже содержат сложные части. Эти кусочки - объекты, напишите код, использующий эти объекты, а сложные части инкапсулируйте в другие объекты и т.д. Отделяй создателя класса от пользователя класса (клиентское программирование). Пользователь класса - простой потребитель, он не должен знать что именно и как происходит внутри ваших классов. Создатель класса должны быть экспертом в создании и проектировки классов, поэтому получаемый класс должен быть максимально прост в использовании. Библиотеки использовать легко, если они "прозрачны" и ясны. Когда Вы создаете класс, старайтесь дать ему имя такое, что бы не нужны были комментарии. Ваша задача сделать интерфейс клиентского программиста концептуально простым. Для этого используйте перегрузку методов, когда нужно и легкий в использовании интерфейс. Ваша модель системы должна производить по минимуму классов, интерфейсов и связей с другими классами, в частности с базовыми. Если ваш проект создает больше чем нужно, спросите себя, нужны ли все эти методы, интерфейсы и связи во время работы программы? Если нет, то их поддержка вам еще встанет боком.
Члены групповой разработки стараются избавляться от ненужных частей проекта, что существенно сказывается на их производительности. Автоматизируй все! Пишите тестовый код в первую очередь (до того, как напишите сам класс) и сохраните его вместе с классом. Автоматизируйте запуск ваших тестов посредством makefile или похожей приблуды. Тогда любые изменения в коде могут быть автоматически проверены запуском теста, а Вы при этом немедленно получите все ваши ошибки. Поскольку Вы знаете о том, что ваши тесты верны и безопасны (ведь так?), то Вы можете больше экспериментировать в поиске правильного решения поставленных задач. Вспомните, что наибольшим повышением производительности в языках программирования стал встроенный тест по проверке типов, а так же по обработке исключений и т.д. Вы должны отойти от создания трудоемкой системы к для созданию тестов, которые будут проверять вашу программу на спецификации. Пишите сперва тестовый код, до того, как Вы напишите ваш класс, в порядке проверки правильности проектировки. Если Вы не можете написать тестовый код, то Вы и не знаете как будет выглядеть ваш класс в действительности. В дополнение, написание тестового кода часто смывает дополнительные возможности или принуждает внести необходимые возможности в ваш класс, а так же пересмотреть модель вашего проекта. Тесты так же служат кодом примеров, показывающим, как нужно использовать ваш класс. Все проблемы проектировки программного обеспечения могут быть выявлены введением дополнительного уровня абстрактного "отрешения". Это фундаментальное правило программных разработок[85] является базой абстракции, основной возможности объектно-ориентированного программирования. "Отрешение" должно иметь смысл (в сочетании с принципом 9). Это означает, что все что "простое" должно быть отдельно (отдельный код в отдельном методе). Если же Вы добавите еще один уровень абстракции, то это будет уже плохо. Создавайте классы настолько атомарными, на сколько это возможно. Давайте каждому классу простое, понятное предназначение. Если ваши классы или ваша система проектирования растет слишком сложной, разделите сложные классы на несколько простых. Наиболее понятным индикатором можно считать абсолютный размер: если класс велик, то есть шанс его разделить на несколько маленьких.
Ключи для предположения по перепроектировке класса:
1) Запутанные операторы: подумайте о полиморфизме.
2) Большое число методов, которые обрабатывают различные типы или операции: подумайте о нескольких классах.
3) Большое число элементов переменных, которые относятся к различным характеристикам: подумайте о нескольких классах. Следите за длинными списками аргументов. При этом вызовы, запись, чтение или обработка методов значительно затруднены. Вместо этого попробуйте переместить метод в класс, где его наиболее часто употребляют или передавайте ему объекты, как аргументы. Не повторяйтесь. Если некий кусочек кода требуется в многих методах дочерних классов, то поместите его в один метод базового класса, а затем вызывайте его из дочернего. Это не только позволит сохранить место, но и позволит с легкостью вносить изменения. Иногда изучение этого общего кода приносит дополнительную функциональность вашему интерфейсу. Следите за выражениями switch и цепочками if-else. Это всего лишь индикатор проверки кодирования, что означает, что Вы выбираете, какой код будет выполнен на основании некой информации (точный тип может быть не известен первоначально). Вы должны заменять этот код посредством наследования и полиморфизма; вызовы полиморфных методов выполняют за вас всю работу по проверке типов, что в результате позволяет иметь более гибкую систему. С точки зрения проектировки, найдите и отделите те вещи, которые могут изменяться от тех, которые всегда постоянны. Это означает, что нужно в системе найти элементы, которые Вы можете изменить без принудительного редизайна, затем инкапсулируйте их в отдельные классы. Вы можете значительно больше узнать об этом в книге Thinking in Patterns with Java, доступной с www.BruceEckel.com. Не расширяйте фундаментальную функциональность посредством подклассов. Если элемент интерфейса для класса важен, то он должен быть в базовом классе, а не добавлен в процессе наследования. Если же Вы добавляете методы во время наследования, то стоит подумать о перепроектировке. Меньше - больше. Начинайте с минимума интерфейса класса, как можно меньшего, что бы только хватало решить поставленную задачу, не пытайтесь предугадать все варианты, как может быть использован ваш класс. Как только ваш класс будет использован, то здесь уже можно посмотреть и расширять его интерфейс. Но все равно, как только Вы "выпустите" класс, то уже будет невозможно раширять его интерфейс, без изменения его кода. Если вам нужно расширить интерфейс существующих методов, добавлением новых аргументов, создайте перегруженный метод с новыми аргументами. "Прочтите" ваш класс в слух, что бы убедиться, что он логичен. Убедитесь, что отношения между базовым классом и дочерним классом есть "это - есть" ("is-a"), а у элементов класса "имеет это" ("has-a"). Во время принятия решения по использованию наследования или композиции, спросите себя, а нужно ли мне использовать приведение к базовому типу? Если нет, то предпочтите композицию наследованию. При этом отпадет необходимость в множестве базовых типов. Если же Вы наследуете, то пользователи могут не без оснований думать, что можно произвести приведение к базовому типу. Используйте элементы данных для разнообразия в значениях, а переопределение методов для разнообразия в поведении. Это означает, что если Вы найдете класс, который использует значения переменных в месте с методами, для того, что бы изменять поведение в зависимости от значений этих переменных, то следует, скорее всего, перепроектировать этот класс. Нужно выразить различие в поведении в подклассы и переопределенные методы. Следите за перегрузкой. Метод не должен выполняться на основе аргумента, вместо этого нужно создать два или более перегруженных методов. Используйте иерархию исключений, желательно наследовать от специального класса в стандартной иерархии исключений Java. Если кто-то обрабатывает исключения, то он может обрабатывать только определенные типы исключений, следующие от этого базового класса. Если Вы добавляете новое дочернее исключение, то существующий клиентский код его все равно обработает, основываясь на базовом типе. Иногда простая агрегация выполняют всю работу. "Система комфорта пассажира" на авиалиниях состоит из различных, отсоединенных друг от друга частей: сиденья, кондиционеры воздуха, видео и т.д., а теперь представьте, что вам нужно создать несколько таких систем в самолете. Вы сделаете частные (новые) элементы и создадите новый интерфейс? Нет, в этом случае, компоненты также являются частью публичного интерфейса, поэтому Вы должны просто создать публичные элементы-объекты. Эти объекты имеют свою собственную реализацию, но при этом они так же безопасны. Знайте, что простая агрегация не то решение, которое может часто применяться, но при его использовании все счастливы. Примите во внимание клиентского программиста и того, кто будет обслуживать ваш код. Проектируйте ваш класс настолько ясно, насколько это возможно для использования. Предвидьте те изменения, которые могут с ним произойти и спроектируйте ваш класс так, что бы привнести их было в него просто. Остерегайтесь синдрома гигантских объектов. Этот синдром - частое несчастье процедурных программистов, которые только начинают программировать в ООП, и кто еще не закончил писать процедурные программы и обычно помещает в своей объектной программе несколько больших объектов и все. Если Вы должны сделать что-то ужасное, то меньшей мере сделайте это внутри класса. Если вам нужно сделать что-то непереносимое, то создайте абстракцию и локализуйте ее в отдельном классе. Это уже более высокий уровень абстрагирования непереносимых элементов, которые будут распространяться с вашей системой. (Эта идиома материализована в шаблоне Bridge). Объект должен не просто содержать данные. Они должны так же иметь и определенные принципы поведения. (Иногда, чистые объекты данных подходят, но только когда они используются для хранения или передачи группы элементов.) Сперва используйте композицию, когда создаете новый класс от уже существующего. Вы должны использовать наследование только, если это требование вашего дизайна. Если Вы используете наследование, где должна быть использована композиция, то тогда ваш дизайн без нужды запутанный. Используйте наследование и переопределение методов для выражения различий в поведении, а поля для выражения различий в положении. Крайним случаем того, чего не нужно делать - наследование различных классов для отображения цвета, вместо использования поля цвета. Остерегайтесь конфликтов. Два семантически различных объектов могут иметь идентичные возможности по действиям или по ответственности, при этом возникает искушение попытаться создать один подкласс от этих классов посредством наследования. Отсюда и возникает конфликт, однако в этом случае нет необходимости оправдывающей создание связи дочернего класса с суперклассом. Поэтому лучшим решением будет создать главный базовый класс, который реализует интерфейс для обоих дочерних классов, при этом потребуется немного больше места, но у вас останутся все преимущества наследования. Остерегайтесь ограничений наследования. В ясном дизайне новые возможности добавляются в наследуемые объекты. Подозрительный дизайн удаляет старые возможности во время наследования без добавления новых. Но правила могут нарушаться, и если Вы работаете из старой библиотеки класса, то было бы лучше ограничить существующий класс в его подклассе так что бы была возможность переработать иерархию наследования, что бы ваш класс был там, где ему положено быть - над старым классом. Используйте шаблоны проектировки, что бы исключить "голую функциональность". Это означает, что если вам нужен только один созданный объект вашего класса, то добавьте комментарий "Создавайте только один". Оберните его в одноэлементное множество (синглтон). Если же у вас имеется много грязного кода в вашей главной программе, которая создает ваши объекты, то, обратитесь к шаблонам создания, например, к методам предприятия, с помощью которых Вы и можете реализовать это создание. Исключение "голой функциональности" не только сделает ваш код более легким для понимания и поддержки, оно так же сделает его более пуленепробиваемым против "доброжелателей" пришедших после вас. Остерегайтесь аналитического паралитизма. Запомните, что Вы должны обычно продвигаться в перед в проекте до того, как Вы узнаете все о нем, а лучшим способом при этом будет узнавать то, что Вы не знаете до того, как Вы к этому приступите. Вы не будете знать решения до того, как Вы получите его. Java сделана по принципу файрволов (брендмауеров), дайте им поработать в ваших интересах. Ваши ошибки в классе или наборе классов не разрушат целостность всей системы. Когда Вы думаете, что Вы хорошо проанализировали систему, создали отличный проект или его реализацию, то критично оцените (проанализируйте ее сквозным методом) всю систему целиком. Покажите систему какому - либо стороннему лицу, кто не участвовал в разработке или консультациях. Взгляд на систему парой "новых" глаз зачастую помогает выявить недостатки и недоделки.
Содержание раздела