Модульность

Э

Та глава посвящена составлению пакетов, классов и методов. Основная ее тема — необходимость упрощения кода методом, который формулируется как "разделяй и властвуй".

В большинстве случаев программирование не должно быть сложным Если оно сложно, то, скорее всего, по причине неправильного проектирования программы.

При написании кода вашей основной задачей должна быть максимальная яс­ность и простота. После завершения проекта проверьте, что все работает четко, за­тем найдите то место, для которого критична производительность, и начинайте ду­мать над оптимизацией кода. Однако чаще всего оптимизация не особенно важна. Не то чтобы совсем не важна, но обычно это не основная цель.

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

Эстетика программирования

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

Когда дело касается эстетики, не существует твердо установленных фактов. Чаще всего неправильным будет писать книгу, не разбивая ее на главы. Но могут быть слу­чаи, когда именно так и стоит поступить. Обычно будет неправильно написать один абзац, растянутый на 20 страниц, но иногда это именно то, что требуется. Обычно не рисуют обнаженных детей, но иногда это нормально. Например, Микеланджело не совершил эстетическую или моральную ошибку, поместив на некоторые свои по­лотна ангелочков и другие обнаженные фигуры. Существует множество стихотворе­ний, и даже рассказов, в которых мало заглавных букв. В некоторых известных рас­сказах нет кавычек вокруг прямой речи.

Конечно, неверно думать, что если в какой-то области трудно установить четкие правила, то эти правила вообще не нужны. Например, тот факт, что нет четкого пра­вила по поводу разбиения текста на абзацы, не означает, что от абзацев следует во­обще отказаться. И если в Библии говорится об обнаженных людях, то и в любой книге можно или даже нужно вставлять изображения голых людей. А тот факт, что известный поэт э. э. каммингз не употребляет в своих стихах заглавные буквы, не оз­начает, что в стихотворчестве следует вообще отказаться от заглавных букв.

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

Правило можно и нарушить, но только если вы уверены, что вы должным обра­зом понимаете его. Никто не сомневается в том, что э. э. каммингз знает, как нужно употреблять заглавные буквы в предложениях. Восклицания "бесноватого фруктови- ка" Экшена (типа "Спасувайтесь! Опась! Гроза — не крупная зла! Горы — страха бо­лее!..") в произведении Гарри Гаррисона "Мир Смерти и твари из преисподней" (Deathworld 6: The Creatures From Hell/Harry Harrison) не следуют стандартным правилам английского языка (спасибо переводчикам — "нарушения" сохранились и в русскоязычной редакции книги), но вряд ли стоит сомневаться, что Гарри Гарри­сон не знал, как правильно писать по-английски. Поэтому упомянутым авторам можно было нарушить некоторые общепринятые правила. Если вы знаете правила, описанные в этой главе, и на самом деле следовали им во многих больших програм­мах, то вы можете начать думать о том, что в некоторых обстоятельствах такие пра­вила можно и нарушить. Но все же в основном я считаю эти правила полезными ре­комендациями, которым имеет смысл следовать.

Эстетика принципа "разделяй и властвуй"

Пакеты и классы проекта могут до некоторой степени рассказать историю созда­ния программы. Понять проект можно не только читая его код, но и читая его клас­сы и пакеты.

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

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

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

Наличие повторяющегося кода в проекте является признаком того, что этот код должен быть разбит на отдельные методы или классы. Другой общий признак — это то, что код трудно повторно использовать. Если вы говорите: "Да ну его, это легче переписать, чем повторно использовать", — знайте, что код написан неправильно.

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

В некотором смысле вам необходимо выдержать две битвы:

■ Первая битва — между хаосом и порядком (ну как тут не вспомнить Роджера Желязны с его "Хрониками Амбера"! — Прим. ред.). Если у вас есть один класс, метод или пакет, выполняющий пять различных задач, то чаще всего этот код плохо организован, труден для понимания и вообще неупорядочен. Поэтому первая битва состоит в создании порядка из хаоса, правильно сегментируя различные части программы.

■ Вторая битва — между порядком и размером. Случается так, что наиболее простым и четким способом упорядочения кода является его запись в одном методе или классе, или в помещении множества различных классов в один пакет. Проблема возникает тогда, когда это чувство упорядоченности застав­ляет вас создавать слишком большие для понимания классы. В некоторый момент многие программисты хотят разбить свой метод, класс или пакет, просто потому что он слишком большой. И в то же время бывает так, что чув­ство упорядоченности подсказывает, что длинные участки кода принадлежат одному пакету, классу или методу. Подобного рода решения не всегда легко принять. В таких трудных случаях и возникает борьба между порядком и раз­мером. Чаще всего большой класс, пакет или метод пытается сделать слиш­ком много. Но иногда большой метод действительно посвящен одной отдель­ной идее. Если в нем нет повторяющегося кода и его легко использовать повтор­но, то зачем его разбивать? Битва между желанием упростить код с помощью его сегментирования и желанием инкапсулировать соответствующий код в одном методе, или классе, или пакете, не прекращается ни на мгновение.

Целью данной главы является попытка примирения этих противоположных сил. А это задача совсем не простая.

Используемость, расширяемость и абстрактность

Дэнни Topn (Danny Thorpe) в своей книге "Delphi Component Design" ("Построе­ние компонентов Delphi") говорит о важности создания таких классов, которые дол­жны быть используемыми, расширяемыми и абстрактными.

Основная мысль, которую подчеркивает мистер Торп, состоит в том, что классы должны создаваться как многократно используемые: "… создатель компонентов все­гда должен стремиться к тому, чтобы они были полезны для тех, кто пишет прило­жения, чтобы их было легко использовать, и чтобы они имели ясный и надежный открытый интерфейс". Здесь он говорит о построении классов, специально создава­емых для использования другими программистами. Но кто в наше время пишет код, который не читают другие? Мы все пишем код, который должен быть полезен другим, так что, если возможно, мы должны следовать советам мистера Торпа.

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

Объекты и область видимости: скрывайте как можно больше

Расширяемость объекта отчасти зависит от правильного манипулирования с приватным, защищенным и открытым разделами класса. Не всегда легко решить, какие части класса будут видимы только другим классам в пределах одного исходно­го файла (приватные) или одного пакета (защищенные), а какие разделы класса бу­дут открытыми для всех желающих.

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

Если два класса так тесно связаны, что им необходимо вызывать защищенные методы друг друга, то они принадлежат одному и тому же классу. Если понятно, что они принадлежат отдельным пакетам, то они никогда не должны вызывать защи­щенные методы друг друга. Должен существовать ясный и простой открытый интер­фейс, Oi! срывающий только необходимые порции каждого класса.

примечаниеИ опять-таки, мы наблюдаем битву, путь к победе в которой совершенно неоче­виден. Мы должны скрывать все данные и все возможные методы за максимально сильными ограничениями видимости. Многие из лучших объектов оставляют толь­ко один открытый метод в качестве точки входа, наряду с конструктором или мето­дом, следующим единому образцу. Но какие методы должны быть открытыми, и ка­кие объекты могут предлагать лишь минимальный интерфейс?

Q Данном разделе я использую слово "интерфейс" преимущественно в абстрактном смысле. Я не обязательно говорю о типе языка Java, Называемом InterfВое. В последнем случае я буду писать его с заглавной буквы.

Необходимо абстрагировать интерфейс класса. Один класс не должен знать слишком много о другом классе. Вместо этого интерфейс между ними должен быть как можно более абстрактным и общим. Часто для этого лучше всего создать Ин гер — фейс Java.

Правила, касающиеся области видимости, просты:

■ Запакуйте все, что можно. Скройте все, что только может быть скрыто, в при­ватных методах в отдельном классе, находящемся в своем собственном исход­ном файле.

■ Не показывайте из вашего класса никаких данных, кроме неизменяемых кон­стант.

■ Старайтесь не создавать открытые методы вашего класса и проверяйте, нельзя ли преобразовать их в приватные методы.

■ Сделайте открытый интерфейс вашего класса максимально простым. В идеа­ле может существовать лишь одна-единственная точка доступа к классу.

В нескольких местах этой книги я говорю о таких делах, которые вы можете вы­полнять, когда слишком устали для настоящей работы. Изменение открытых мето­дов на приватные — одна из таких задач, которую вы можете выполнять, ожидая те­лефонный звонок, или когда у вас есть только 20 минут между совещанием и обе­денным перерывом.

Вопросы производительности

Хотя на первый взгляд это может быть и не очевидно, простой код обычно помо­гает разработчикам создавать наиболее быстрые и устойчивые программы. Код, раз­битый на отдельные объекты, легче повторно использовать. Оказывается, что По­вторное испОльзование обычно явЛяется самым леГким способом создания неболь­Ших быстрых программ.

Когда я начал пользоваться описанной в этой главе техникой, я боялся, что в ре­зультате моих усилий по разбиению кода на меньшие методы и объекты он станет громоздким. Но получилось все наоборот. Я повторно использовал код созданных мною маленьких методов и объектов и таким образом избежал повторения кода. Мои программы стали меньше и быстрее, чем они были бы, если бы я использовал исходный, как я думал, более эффективный, код.

Я боялся, что издержки, связанные с созданием объектов и вызовами методов, окажутся чересчур дорогостоящими. Лет десять-пятнадцать назад в этом был бы ка­кой-то смысл. Но современные компиляторы и процессоры в значительной мере отодвинули эти заботы на задний план. Вызов метода на процессоре с тактовой час­тотой 2 МГц занимает настолько мало времени, что практически не оказывает ника­кого влияния на вашу программу.

А с другой стороны, метод из 30 строк содержит в себе, скорее всего, не менее 20 вызовов методов JDK. Предположим, что вы разбили его на два метода по 15 строк. Добавьте в новый метод еще один вызов, и вы получите не 20 вызовов, а 21. И что, вы думаете, что увеличение количества вызовов с 20 до 21 окажет серьезное влияние на производительность вашей программы? Если только вызов метода не находится в середине какого-нибудь огромного и особо чувствительного ко времени цикла, от­вет таков: абсолютно нет. Разницы не будет никакой Один проход сборщика мусо­ра гораздо больше повлияет на вашу программу. Суть здесь в том, что многие разра­ботчики непонятно почему боятся вызывать свои методы, в то время как они вовсю обращаются к JDK, буквально в трех из каждых четырех строк кода.

Кроме того, код, разбитый на отдельные классы и методы, гораздо легче повтор­но использовать. Поэтому цель заключается в повторном использовании кода.

Вспомните, о чем я говорил ранее: если в реальном программировании вы созда­ете потомка класса JPanel Или JFrame, Вы должны создавать либо полностью мно­гократно используемый класс, либо иметь практически нулевую вероятность по­вторного использования кода класса. Если вы создает обработчик события для кнопки JButton, То этот метод ActionPerformed Должен не выполнять практичес­ки ничего, кроме вызова другого отдельного объекта, выполняющего реальные дей­ствия. Если вы не можете повторно использовать всю панель или фрейм, не поме­щайте сколько-нибудь серьезный код в обработчики событий для панели или фрей­ма. Просто включите код, обращающийся к другому объекту, который и выполняет всю работу.

Например, если вы пишете код для передачи файлов с использованием HTTP — протокола, не помещайте этот код в потомка JPanel Или JFrame, Кроме случая, ког­да вы делаете многократно используемым весь класс. Вместо этого поместите ЭТОТ КОД В Класс С Именем HTTPTransfer. Тогда ваши ПОТОМКИ JPanel ИЛИ JFrame Смогут вызвать этот класс — так же, как и любые другие потомки JPanel Или JFrame. Этот код можно многократно использовать. Код, находящийся внутри ме­тода JButtonlsActionPerformed Потомка JFrame, ПОВТОРНО ИСПОЛЬЗОВаТЬ обычно Невозможно. Не помещайте в эти методы что-либо серьезное.

Простой, легко читаемый код хорош и сам по себе. Метод ActionPerformed, Со­держащий одно — или двухстрочный вызов класса, передающего файл через HTTP, гораздо легче прочитать, чем метод ActionPerformed, Который содержит 20—30 строк кода, выполняющего пересылку.

Здесь самое главное — не забывать назначать каждому классу только одну глав­ную функцию. Если потомок JPanei Будет содержать одну кнопку, и эта одна кноп­ка будет вызывать фрагмент кода передачи файла из 20 строк, то это, пожалуй, нор­мально. Но если потомок JPanel Содержит 20 кнопок, и каждая из них вызывает свой 20-строчный метод ActionPerformed, То получится не класс, а какая-то каша. В таком случае вы должны использовать JPanei Для ввода данных пользователя, и затем предоставить обработку этих запросов пользователя отдельным объектам, ко­торые легко использовать повторно.

Когда сложности уже хватит?

Все программы, состоящие из сотен тысяч строк, сложны по определению. По­этому, если вы пишете серьезные программы, то столкнетесь с большой сложнос­тью. Выбор здесь в том, что можно либо написать программу из сотен тысяч строк, которая сложна и не поддается отладке и сопровождению, либо написать програм­му из сотен тысяч строк, которая сложна, но все-таки управляема.

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

В больших программах нет приливов и отливов — только приливы. Вам все вре­мя будет необходимо писать код, не являющийся повторно используемым. Вам все время будет необходимо оставлять много повторений в ваших программах. Битва с этими необходимостями просто-таки бесконечна.

Во-первых, написание повторно используемого объекта требует большего време­ни, чем просто напихать код в текущий метод или класс вашей программы. Но если вы сделаете свой код модульным, то в конечном итоге это сэкономит вам время.

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

Лучше всего, если вы полюбите писать простой и легко читаемый код. Простой код — это элегантный код. В нем есть эстетическое очарование, не сравнимое с оча­рованием никакого другого кода, каким бы сложным он ни был.

Java в качестве спасителя

Когда язык Java только появился, тысячи программистов, долго работавших в мире C++, считали его каким-то чудом. Проекты, которые на C++ необходимо было разрабатывать годами, могли быть завершены на Java буквально за несколько месяцев. Шестимесячный C++-πpoeκτ на Java можно было завершить за два месяца.

Откуда такая большая разница? Почему Java значительно легче, чем C++?

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

Посмотрите на стандарты в мире Java, которые на самом деле используются мно­гими и многими программистами. Среди классических примеров — JUnit, Ant, Java — классы для работы с сетями и классы Applet и JApplet. Это все примеры про­стого и легко читаемого кода, ворвавшегося в общество разработчиков.

Планирование кода

Следующий раздел разбит на три подраздела, а некоторые из них разбиты на еще меньшие подразделы. Первая тема — техника обнаружения объектов. Затем я расскажу о составлении методов и завершу кратким обсуждением обнаружения па­кетов.

Обнаружение объектов в задаче

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

Вернемся на минутку к задаче, о которой шла речь в главе 6: о создании в Java средства наподобие утилиты Grep. Эта утилита позволяет искать заданную строку сразу во множестве файлов. Например, вам может понадобиться найти все вхожде­ния слова "DefaultModel" во всех исходных файлах вашего проекта. В JBuilder имеет­ся подобное средство, которое можно вызвать с помощью пункта меню Search ∣ Find In Path (Поиск | Найти в каталоге). Но в данной главе я буду говорить только об от­дельной версии такой программы.

Подобный инструмент должен предоставлять пользователям интерфейс, позво­ляющий задавать каталоги, в которых требуется выполнить поиск, место для ввода искомой строки и, скорее всего, расширения, чтобы отфильтровывать файлы, в ко­торых поиск проводить не нужно. Например, если вы хотите провести поиск в своих исходных файлах, необходимо просматривать файлы с расширением .java И игно­рировать файлы с расширениями .e×e Или .class

Имея такую задачу, какое наихудшее с точки зрения конструкции программы ре­шение может предложить программист? Наихудший случай — это один класс, со­держащий один метод объемом порядка 10 000 строк. Проблема состоит в том, что один метод (main) выполняет множество различных задач. Он выполняет просмотр каталогов, открывает и закрывает файлы, выполняет поиск внутри файлов, отобра­жает информацию пользователю и принимает информацию от него. Это слишком много для любого метода.

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

Представим себе решение

Мэри Кэролайн Ричардс говорит: "Воображение помогает нам воспринимать ре­альность, которая еще не полностью материализовалась". У нас есть одна задача, в данном случае — написать программу типа grep Поэтому вначале мы представим программу с одним методом под названием grep.

Очевидно, что такого рода программе понадобится несколько классов, дабы над­лежащим образом разделить и властвовать над задачей, которую необходимо ре­шить. Ясно, что для решения этой задачи потребуется как минимум три класса:

■ Класс наподобие потомка JPanel ИЛИ JFrame, с помощью которого можно принять информацию, введенную пользователем, и отобразить ему результа­ты поиска

■ Класс, перебирающий последовательность каталогов и отбирающий множе­ство файлов с заданным расширением. На самом деле в мире Java это могут быть два класса: один для перебора каталогов, а другой для фильтрации фай­лов.

■ Третий класс, просматривающий все байты конкретного файла для поиска вхождений нужной пользователю строки.

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

■ Сначала немного планирования.

■ Затем немного кодирования

■ Затем тестирование написанного кода, дабы убедиться, что он работает. Здесь может потребоваться средство JUnit, интегрированное в JBuilder, которое бу­дет рассматриваться в главе 30, "Тестирование модулей с помощью JUnit". Возможно, уже на этой стадии потребуется корректировка связей.

■ Затем вы показываете свое творение кому-либо и спрашиваете, правильно ли оно выполняет поставленную задачу.

К этому времени вы, несомненно, многое узнали о задаче, для которой предназ­начена ваша программа. Вы видите новые решения задач, которые не видели рань­ше, и вы видите новые задачи.

В результате небольшого опыта реального программирования вы можете узнать следующее:

■ Перебор каталогов Windows не похож на перебор каталогов Linux, т. к. в них отличаются системы работы с дисками и разделами.

■ При переборе файлов вы можете встретить файл, используемых другой про­граммой. Поэтому вам придется выбрать способ работы с заблокированными файлами.

■ Некоторые пользователи хотят провести поиск, чувствительный к регистру, а другие — нечувствительный.

Памятуя обо всей этой информации, вы планируете, как решать новые задачи. Затем вы пишете часть кода, немного корректируете связи и запускаете старые тес­товые программы, чтобы удостовериться, что вы не затронули ничего лишнего. По­том вы пишете другие тестовые программы — для проверки новых функций вашей программы. И, наконец, вы показываете все это кому-то и спрашиваете, работает ли оно у них.

Эти кто-то наверняка найдут какие-то ошибки и попросят добавить новые воз­можности. Может быть, они захотят, чтобы программа была многопоточной, чтобы можно было проверять некоторые файлы, еще не закончив поиск. И так далее…

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

Уроки, которые вы извлекаете из создания программ наподобие этого примера утилиты grep, состоят в том, что Каждый объект должен выполнять одну, и только одну, задачу. Если вы хотите нагрузить на один класс слишком много функций, груз постоянного изменения, улучшения и корректировки связей вашей программы мо­жет стать практически неподъемным.

Создание открытых методов класса

Создание интерфейса для какого-то объекта — весьма деликатный момент. Ин­терфейс класса должен быть максимально абстрактным. Этот интерфейс должен, насколько возможно, скрывать реализацию инкапсулированного в нем объекта. На­пример, он не должен непосредственно показывать пользователю инкапсулирован­ные в нем массивы.

Составление методов

Пора переместить внимание с написания объектов на написание отдельных ме­тодов. Эта тема тесно связана с обсуждением в предыдущем разделе, поэтому я пе­рейду к ней без каких-либо предисловий.

Никогда не копируйте код

Ox уж эта команда копирования блока текста! Иногда она полезна, но иногда нет команды более коварной.

примечаниеПроблема с копированием блока кода состоит в том, что у вас получается две ко­пии кода, выполняющего одну задачу. Это делает программу неуклюжей. Хуже то, что исправления ошибок в одном объекте должны проводиться и в другом объекте. Это должно быть исключением, а не правилом, когда мы проводим подобные ис­правления в двух, а не в одном объекте. А если вы только включились в проект и даже не подозреваете, что где-то существует копия найденной вами ошибки, то вы просто не будете знать, что исправлять надо сразу два места программы.

Кое-кто обладает феноменальной способностью помнить даже мель­чайшие детали. Но обладание таким даром не является оправданием для написания "кри­воколенного" кода. Вы не должны применять это на практике просто потому, что може­те неделями помнить все места, куда скопировали отдельные участки кода. Вместо это­го лучше направьте свои таланты на какую-нибудь полезную цель, вроде изобретения способа многократного использования кода, вместо его копирования. Разве не интерес­нее найти способ многократного использования кода, чем решить проблему просто за счет своей памяти? Если я вас не убедил, посмотрите фильм Альфреда Хичкока "39 Ша­гов" ("The 39 Steps”). Возможно, быть мистером Памятью не так уж и увлекательно, как Поначалу кажется.

Изолируйте отдельные задачи

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

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

О длинных методах

Я думаю, что в самих длинных методах нет ничего плохого. Но я считаю, что если у вас много длинных методов, то, скорее всего, тут что-то не так.

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

Как узнать, что некоторый метод действительно должен быть длинным? В таких случаях нужно обращать внимание на два момента:

■ Он содержит длинный оператор Case Да, операторы Case Приводят к появ­лению длинных методов. Такова жизнь. Единственная опасность таких ситуа­ций заключается в том, что каждый раздел оператора Case Может стать эда­
ким мини-методом. Если один из вариантов оператора case длиннее, чем две-три строки, и предположительно он может быть повторно использован, то вам стоит подумать над тем, чтобы превратить его в отдельный метод, вы­зываемый из оператора case.

■ Простые операторы if-else также могут привести к длинным методам. Опять-таки, если какая-то часть оператора if-else становится очень длин­ной, вам следует подумать над выделением ее в отдельный метод, вызывае­мый из оператора if-else.

Более сложный случай — вложенные операторы if-else, наподобие показанных в листинге 22.1.

Листинг 22.1. Сложный метод IiandIeOptions Содержит несколько вложенных операторов If

Void say (String s)

{

System. out. println(s);

1

∕∕ Цитаты в этом методе взяты из стихотворений Уильяма Блейка.

Void handleθptions(boolean a, boolean b, boolean С, Boolean d, boolean e, boolean f, boolean g)

{

If (a == true)

(

If (b == true)

(

Say("Радости наполняют. Огорчения толкают вперед.");

}

Else if == false)

{

If (d == true)

(

Say("Стыд скрывает гордость.");

)

Else

{

Say("Стоячая вода может протухнуть.");

)

}

)

Else if (e == false)

(

If (f == true)

{

Say("Слишком большое горе смеется, слишком большая радость плачет.");

)

Else {

If (g==true)

лучшая похвала.");{

Say("Обвинения дурака —

}

Else

<

Вау("Чтобы создать даже маленький цветок, нужны сотни лет труда.");

}

}

}

}

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

Листинг 22.2. Расчленение сложного оператора If На меньшие методы

Void say (String s)

{

System. out. println(s);

)

Void doOptionλ(boolean b, boolean c, boolean d)

{

If (b == true)

{

Say("Радости наполняют. Огорчения толкают вперед.");

}

Else if = false)

(

If (d == true)

1

Say("Стыд скрывает гордость.");

)

Else

{

Say("Стоячая вода может протухнуть.");

)

)

)

Void doOptionB(boolean e, boolean f, boolean g)

{

If (f = true)

{

Say("Слишком большое горе смеется, слишком большая радость плачет.");

)

Else

(

If (g=true)

<

Say("Обвинения дурака — лучшая похвала.");

)

Else

{

Say("Чтобы создать даже маленький цветок, нужны сотни лет труда.");

)

)

)

Void handleθptions(boolean a, boolean b, boolean С, Boolean d, boolean e, boolean f, boolean g)

{

If (a ≈ true)

{

DoOptionA(b, С, D) ;

)

Else if ≈ falsq)

{

DoOptionB(f, d, g);

1

}

Первое, что вы, возможно, заметите при сравнении листингов 22.1 и 22.2 — это то, что листинг 22.1 короче на 9 строк. Наверно, второе, что вы заметите — метод handleθptions понять труднее в листинге 22.1 и гораздо проще в листинге 22.2. Если вы приглядитесь еще внимательнее, вы увидите, что оба метода doθptionA и doθptio∏B также относительно более легко управляемы.

C помощью тактики "разделяй и властвуй" мы смогли упростить код, представ­ленный в листинге 22.1. Это может пригодиться или не пригодиться, в зависимости от того, сможете ли вы повторно использовать два новых метода.

Используйте пакеты для крупных подзадач

Если вам попалась задача, которая требует для ее завершения пять или шесть объектов, их следует поместить в отдельный пакет. Например, если вы создаете эле­мент управления, используемый для получения файла через Internet, его разбора и отправки по электронной почте, то почти наверняка вам следует создать, по мень­шей мере, один пакет для размещения этой части своей программы. Если вы помес­тите все эти методы и объекты в тот же самый каталог, что и вся остальная програм­ма, то это будет уже слишком.

C ума сойти с этими событиями

Я хочу уделить несколько минут общему обсуждению программы FiieRename, Которая находится в каталоге utilities в сопровождающих книгу материалах. Эта программа демонстрирует интересный подход к созданию модульного и легко по­вторно используемого кода.

Программа FileRename Полностью построена на событиях и интерфейсах. После инициализации программы ни один объект не вызывает другой напрямую. У глав­ных объектов программы нет никаких общих данных, кроме тех, которые вначале необходимы Framei Для создания работающих в дальнейшем экземпляров объек­тов. Но сам Framel Не работает. Он лишь создает другие объекты, а затем передает им всю власть.

Эта модель повышает устойчивость программы и делает каждый объект по-на­стоящему отдельной единицей. При разработке подобных программ вы должны ду­мать о создании объектов, которые способны существовать полностью автономно. Если вы хотите создать настоящую архитектуру Plug-and-play, вы должны убрать все

Взаимосвязи между объектами программы. Вы создаете простые маленькие маши­ны, которые могут существовать самостоятельно.

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

Чтобы оценить сложность, присущую этому методу, подумайте над следующим вопросом: если пользователь изменяет какие-либо данные в программе, все заинте­ресованные части программы должны быть извещены об этом с помощью событий. Один метод не может просто опросить текущее состояние другого объекта с помо­щью вызова метода. Наоборот, если один объект изменяется, то он должен оповес­тить об этом изменении другие объекты. Любой объект, которому нужно знать об изменениях состояния этого объекта, должен поддерживать соответствующий ин­терфейс ожидания событий и зарегистрироваться в качестве их слушателя.

Считаю ли я, что это лучший способ писать настоящие объектно-ориентирован­ные программы? Пожалуй, так далеко я не замахиваюсь. Использование методов ус­тановки и выборки настолько проще, что мне не остается ничего другого, как во многих случаях считать их лучшим выбором. Но в некоторых случаях эта модель вполне привлекательна.

Программа FiIeRename

Пример программы, рассматриваемый в данной главе, появился в силу необхо­димости оперировать с файлами, которые я выгружал из сайта Www. emusic. com. Подписчики этого сайта, внося месячную абонплату, могут выгружать из него МРЗ — файлы. Имена музыкальных файлов, выгруженные с этого сайта, имеют следующий формат:

ИсполниФель-Альбом-НомерДорожки-НааваниеПесни. mp3

Этот формат может порождать очень длинные имена, и некоторые из них превы­шают максимальную длину, поддерживаемую моими программами прожига CD- RW. Поэтому я написал программу, которая разбивает эти имена на каталоги с именем "Исполнитель", Подкаталоги с именем "Альбом", И затем последователь­ность файлов с именами "Номердорожки-Нааваниепесни". Например, выгрузка мо­жет породить каталог Tomjwaits\Muie_variations, а песни могут называться 01-Big_In_Japan. mp3, 02-Lowside_Of_The_Road. mp3 Ит. Д.

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

рис. 22.1. визуальное представление программы ^fierename показывает потоки событий от класса filehandler к классам controlpanel и displaydataРешил, что большинство классов будут общаться друг с другом с помощью сооб­щений, а не методов выборки и установки.

Например, один класс программы FileRename посылает событие ActionEvent классам ControlPanel И DisplayData при выборе пользователем нового катало­га, как показано на рис. 22.1. Классы ControlPanel и DisplayData могут уз­нать текущий каталог, только получив это сообщение. Они не проверяют текущий каталог сами, и они не пользуются мето­дом выборки для получения имени теку­щего каталога от класса FileHandler.

Чтобы все это работало, нужно, чтобы ни класс ControlPanel, Ни класс DisplayData Не могли изменять эти данные. Необходимо, чтобы только один объект мог изменять эти данные, и он должен извещать другие объекты об измене­нии данных. Если вы не выполняете упомянутые правила, в результате получится хаос. C другой стороны, если вы выполняете эти правила, получается очень четкое распределение задач и упорядоченная структура всей программы.

Один из способов проверки логики при создании подобных программ — удосто­вериться, что поток данных направлен только в одну сторону. Например, вы можете начертить диаграмму классов своей программы. Если сообщения передаются от класса А к классу В, нарисуйте на диаграмме стрелку из А в В. Проблемы возникают тогда, когда класс А указывает на класс В, а класс В указывает на класс А. Ситуации подобного рода быстро становятся неопределенными. Лучше всего, когда информа­ция передается только в одном направлении.

Резюме

Эта глава была в книге своего рода "белой вороной", поскольку в ней практичес­ки отсутствовал код. Вместо этого я постарался донести до вас несколько простых идей, большая часть которых может быть выражена короткими предложениями:

■ Разделяй и властвуй.

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

■ Еще один признак, что что-то не так — трудность повторного использования кода.

■ Каждый метод должен выполнять одну, и только одну, задачу. Единственное возможное исключение — если вы уверены, что эти задачи никогда не будут повторно использованы.

■ Каждый класс должен выполнять одну, и только одну, задачу. Никакая другая концепция не может сэкономить вам столько времени и хлопот, как простая идея деления кода на отдельные многократно используемые классы.

■ Используйте пакеты для инкапсуляции крупных задач, содержащихся в не­скольких классах

■ Если вы хотите создать настоящую архитектуру plug-and-play, вам необходимо разорвать взаимозависимости объектов программы.

■ Многократно использование обычно является самым простым способом со­здания небольших и быстрых программ.

■ Создавайте многократно используемые методы. Не позволяйте повторяюще­муся коду появляться в своих программах.

■ Скрывайте все, что только можно, в приватных методах отдельного класса, который находится в своем собственном исходном файле.

■ Не показывайте никакие данные вашего класса, кроме неизменяемых кон­

Стант.

■ Снова и снова просматривайте открытые методы своего класса, чтобы, воз­можно, найти способ сделать их приватными.

■ Делайте открытый интерфейс своего класса максимально простым. Идеаль­ный случай — одна точка доступа к классу.

■ При создании классов простота гораздо лучше сложности.

■ Правила созданы для того, чтобы их нарушать.

Напрягите немного свои мозги, чтобы найти в своем коде объекты и методы, ко­торые необходимо выделить отдельно. В отличие от комментирования кода и выбо­ра имен переменных, эта задача может обескуражить даже самых головастых про­граммистов. Здесь действительно может потребоваться некоторое напряжение серо­го вещества, чтобы в сложной программе найти уникальные или многократно используемые объекты. Но если вы сможете найти их, то вы сможете написать са­мый краткий, самый быстрый и самый простой в сопровождении код по сравнению со всеми своими коллегами.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *