![]() |
Портируем, рефакторим, оптимизируем
В нескольких топиках обсуждались вопросы оптимизации кода и возникла идея рассмотреть это на конкретном примере: взять готовый код, причесать и оптимизировать. Поскольку предложенный пакет классов на AS2, то добавился еще один шаг - портирование на AS3.
Собственно код, который будем использовать взят с сайта Helen Triolo: http://www.flash-creations.com/notes...svgtoflash.php Немаловажным моментом является то, что Helen разрешает (даже приветствует) подобные действия с ее кодом и своими действиями мы не нарушаем ее законных интересов.Хочется, чтобы процесс был коллективным, но чтобы как-то упорядочить и на выхлопе получить не тонну обсуждений, а что-то вроде статьи, прошу придерживаться следующего регламента: - до полного окончания прошу никого в этот топик не писать; Если вы хотите получать уведомления, просто в левом верхнем углу кликните на иконку "Подписаться на эту тему". - я выкладываю код - результат очередного этапа, с пояснениями что сделано и зачем. - предложения, вопросы, обнаруженные ошибки участники скидывают мне на мыло: ivan.dembicki на gmail, с сабжем "Flasher.ru - Портируем, рефакторим, оптимизируем". - мы их приватно обсуждаем, затем я резюмирую здесь результат, разумеется, с указанием авторства. - выкладывается исправленный код с пояснениями что сделано и зачем. - после этого переходим к следующему этапу. Буду очень благодарен, если кто-то из модераторов согласится помочь в поддержании порядка - удалении нерегламентированных постов. Процесс делим на этапы: I. Портируем II. Рефакторим III. Оптимизируем Поехали. |
Подготовка
Вложений: 1
Заходим на сайт http://www.flash-creations.com/notes...svgtoflash.php идем в конец страницы, открываем окно downloads и скачиваем себе на комп svgtoflash.zip - он 11-й по списку.
Создаем новый AS3 проект (я использую FDT и вам рекомендую), называем его SVGToFlash, затем в винде открываем папку SVGToFlash и закидываем в нее содержимое ZIP файла. В FDT выделяем папку SVGToFlash и жмем F5. Должна получиться структура файлов как на прилагаемой картинке. Вложение 19008 |
Портирование. Задачи
Теперь мы можем приступисть к этапу портирования проекта на AS3.
Задача этого этапа: при минимальном количестве изменений в проекте сделать его работоспособным. На этом этапе мы стараемся по возможности не касаться логики приложения, его структуры и именований. Не забываем: мы нацелены на то, чтобы проект просто запустился под AS3 и дал результаты, аналогичные AS2. |
Начинаем портировать
Подготовка FLA файла.
Открываем во Flash файл svg_displayinflash.fla. Изменяем настройки файла: File - Publish Settings - Flash устанавливаем FlashPlayer 9 и ActionScript 3.0 Заходим в настройки ActionScript 3.0 и устанавливаем все галочки кроме Strict Mode. OK.OK. Затем добавляем папку src в путь к классам: Edit - Prefences - ActionScript - Actionscript 3.0 добавляем строку ./src и выставляем ее второй по порядку. OK. Выделяем кадр actions и закомментируем единственную строку кода // #include "svg_displayinflash.as" Затем задаем Document class: SVGDisplayInFlash, Flash ругнется отвечаем OK. На этом пока заканчиваются изменения во FLA файле, сохраняем. Переходим в FDT, в проекте создаем папку src, добавляем ее в class path: правый клик на папке, Source Folder - Add to Classpath. Создаем в этой папке новый класс: SVGDisplayInFlash - если помните, мы класс с этим именем задали как Document Class. Поскольку у нас в руте есть именованые объекты holder и msg, инициализируем их в этом классе: Код AS3:
Код HTML:
SVGDisplayInFlash.initInstance() |
Повторяем структуру
Вложений: 1
Для того, чтобы файлы AS2 нам не мешались, создадим папку temp и забросим все AS2 файлы в эту папку.
Затем создадим иерархию папок com.itechnika.svg и создадим пустые классы Math2, PathToArray, String2. В итоге должна получиться вот такая картина происходящего: (спасибо, iNils за помощь в публикации материалов) |
Портирование классов пакета com.itechnika.svg
Порядок портирования классов определяестя следующим образом: вначале портируем классы, которые не требуют других классов проекта.
Затем переходим к тем классам, которые используют только портированные классы. Внутри классов действуем по этой же схеме: вначале портируем те функции, которые не используют других пользовательских функций, и затем переходим к функциям, которые используют уже портированные. Следуя этой логике, портирование классов начнем с Math2. Для этого откроем созданный нами пустой класс Math2, и рядом AS2 версию этого класса. Скопируем содержимое AS2 класса кроме первой и последней строки и вставим в тело AS3 класса. Оценим степень разрухи: всё не так уж и страшно. Явных ошибок FDT не нашел, только подсветил отсутствие или неверную типизацию. На этом этапе мы не будем приводить этот код в порядок, поскольку наши изменения могут повлиять на другие классы. Проделаем аналогичную операцию с остальными классами в папке svg. Класс String2, а его мы портируем вторым, также не доставил никаких хлопот. В классе PathToArray потребовалось только импортировать XMLNode: import flash.xml.XMLNode; Тестирование. На данном этапе мы не можем качественно протестировать соответствие кода стандартам AS3. Но минимальный тест всё-таки сделать необходимо. Поскольку класс PathToArray использует и Math2 и String2, то нам для теста достаточно создать экземляр такого класса для того, чтобы компилятор Flash сказал свое веское слово. Для того, чтобы создать экземпляр класса PathToArray нам потребуется посмотреть с какими параметрами он вызывается. С этой целью найдем исходный ZIP файл, распакуем его в папочку на рабочем столе, откроем класс PathToArray и добавим трейсы: Код AS3:
Теперь можем добавить тест. В классе SVGDisplayInFlash добавим метод testPathToArray: Код AS3:
Результатом компиляции станет ругань компилятора. При ближайшем рассмотрении видим, что нет ничего страшного: дважды он ругается на то, что Number не может быть undefined и несколько раз на дублирование объявления переменных. Это несложно исправить. В соответствующих местах заменяем проверку на равенство undefined на функцию isNaN: Код AS3:
Компилируем еще раз и убеждатемся в том, что компилятор не выдает ошибок. Если это так, то можем переходить к следующей части. |
Финальный этап портирования
Вложений: 1
На этом этапе мы портируем svg_displayinflash.as, который представляет собой код инициализации приложения.
Код состоит из двух функциональных частей: загрузка данных в SVG формате и отрисовка полученного пути. Загрузка данных - дело известное, начнем с этого. Добавим в класс SVGDisplayInFlash методы инициализации загрузки и обработчик полученных данных: Код AS3:
getSVGData(SVG_DATA_URL); Если в результате компиляции не возникло проблем, то можем переходить к следующему шагу: портированию метода getShapes и процедуре его вызова. Копируем и вставляем этот метод в класс SVGDisplayInFlash и видим, что FDT подсвечивает множество ошибок. Как это ни странно, но это очень хороший признак, что основная масса ошибок пришлась на последний этап портирования и говорит о правильно избранной стратегии действий. Сейчас у нас развязаны руки и мы можем вносить правки в код, не волнуясь о том, что это может чем-то навредить остальной части проекта. Однако мы не забываем, что на этом этапе наша задача - сделать код работоспособным с минимальными изменениями. Копируем и вставляем объявления переменных, делаем их приватными. Если мы внимательны, то увидим, что переменная x будет конфликтовать со свойством мувиклипа, в котором мы ее объявили. Если мы невнимательны, то компилятор Flash об этом нам напомнит при первом же тесте. Переименуем ее в svg: Код AS3:
Код AS3:
Приступаем к портированию обработки загруженных данных. Пока мы можем пропустить обработку ошибок, поэтому просто переносим строки инициализации из оригинального метода xmlLoaded в метод onSVGLoaded: Код AS3:
Добавляем парсинг полученных данных перед первым обращением к объекту svg: Код AS3:
Код AS3:
Что же нам позволило даже не вникая в суть кода добиться успеха? Методология. Мы следовали простым принципам и в каждый момент делали только то, что соответствовало нашей стратегии: - мы портировали код в строгом порядке: вначале портировались классы не использовавшие других пользовательских классов, только затем классы, использовавшие уже портированные классы, до тех пор, пока таким образом не закончили последний. - мы не изменяли код без крайней на то необходимости и оставили это на самый последний момент. - мы тестировали код при первой возможности. Чего мы избегали: - мы не пытались улучшить код, добавить недостающую типизацию, или изменить логику кода. Мы оставили это для следующих этапов: рефакторинга и оптимизации. |
Начинаем рефакторить
Итак, после успешного портирования мы переходим к следующему этапу - рефакторингу.
Вначале давайте определимся с тем, что такое рефакторинг, зачем он нужен, и затем перейдем к задачам, которые у нас стоят на этом этапе. Код, который мы портировали писался много лет назад. С тех пор изменился не только язык, но и культура написания кода, редакторы кода и общее понимание того, что такое хороший код. Рефакторинг - это улучшение таких свойств кода как читабельность, прозрачность, сопротивляемость ошибкам, а также облегчение его повторного использования, поддержки и дальнейшего развития. Рефакторинг состоит из множества небольших шагов не изменяющих поведения программы. Обратите внимание на то, что здесь ни слова не говорится об увеличении производительности кода, поскольку этот процесс, как правило, затрагивает логику программы, что на этапе рефакторинга недопустимо. Знающие люди могут возразить против самой постановки задачи: рефакторинг не один большой этап, а регулярно выполняемая процедура в процессе программирования. Я, конечно в курсе, но первый рефакторинг после портирования - довольно цельный и объемный этап работ не сопоставимый с обычным рефакторингом, проводящимся в процессе написания программы. Именно поэтому мы уделяем ему такое внимание, что даже выделяем в отдельный этап. Теперь можно определиться с целями: а что же конкретного в итоге мы желаем от нашей программы? Есть две полярные цели: просмотрщик svg файлов и редактор svg файлов. В первом случае стоит задача максимально производительного построения изображения, а во втором - возможность последующего расширения функциональности. Как уже было сказано выше, на этапе рефакторинга нас не интересует производительность приложения, но очень важны другие свойства кода. Этим требованиям более всего отвечает направление на возможность развития проекта в будущем вплоть до редактора svg файлов. А самой первой задачей рефакторинга станет наведение элементарного порядка в коде, до такой степени, что FDT перестанет нам подсвечивать ошибки. |
рефакторим String2
Первый класс, который мы поправим - String2. Он самый короткий, и не использует других наших классов. И для примера я поподробнее остановлюсь на самом процессе.
Подсвечиваются всего три ошибки: в методе shrinkSequencesOf не задана типизация локальных переменных. Зададим ее: Код AS3:
Займемся именованием. Для начала заменим имена аргументов функции. Думать тут особо не надо, поскольку автор позаботилась о комментариях: s заменяем на originalString, ch заменяем на characterToBeFound. Затем, поскольку сокращений мы не любим, заменим: len на length idx на startIndex idx2 на endIndex rs на resultString Также добавим фигурные скобки вложенному while. Давайте сравним, что было и что стало: Код AS3:
Обратите внимание на еще один положительный фактор: в процессе мы всё еще не изменяли логику программы, но уже начали более детально знакомиться с проектом. К тому моменту, когда мы закончим рефакторинг мы будем довольно неплохо представлять что в проекте имеется, где находится и как примерно работает. |
рефакторинг класса PathToArray
Следующий класс для причесывания - PathToArray. Он хоть и дает большое количество ошибок, однако подавляющее большинство - по одинаковой причине, и их можно исправить без существенных усилий.
В методе extractCmds динамически создается объект, содержащий перечень значений стандартных цветов. Эти цвета неизменны, а объект может быть использован неоднократно. Это прекрасный повод сделать объект статической константой и инициализировать его в теле класса. Так и поступим. Вырезаем из метода всю инициализацию объекта colors, до строчки yellowgreen : 0x9acd32 включительно и вставляем с тело класса непосредственно после объявления класса. Затем изменяем объявления и переносим закрывающую скобку в конец присвоений значений цветам. Должно получиться так: Код AS3:
В итоге должно получиться так: Код AS3:
Изучив текущее состояние увидим, что очень много ошибок дает нетипизированный доступ к атрибутам. Что-ж, создадим специальный метод доступа и заменим на него обращения к атрибутам. Код AS3:
заменяем "node.attributes." (с точкой на конце!) на "getAttribute(node, ", в результате чего редактор расцветет массой ошибок. Это очень хорошо. Кликая на красные точки справа от скроллера мы переходим от ошибки к ошибке и ставим закрывающую скобку сразу после первого слова, следующего за "node". Заодно берем это слово в кавычки, поскольку это имя атрибута. В процессе мы обнаруживаем еще одну прелесть рефакторинга: мы обнаружили ошибку несоответствия типов в строке: Код AS3:
При обнаружении ошибки нужно четко понимать, что процесс рефакторинга останавливается и начинается другой процесс - исправление ошибки логики. Обнаружение ошибки - серьезное происшествие и очень хорошее: будучи скрытой мы могли голову сломать откуда она берется и найти только с большим трудом. В данном случае это локальная ошибка, не влекущая за собой цепочку других. Но всё-таки рассмотрим ее отдельно. |
Исправление ошибки
Вначале проанализируем ошибку.
Переменная hasRotate объявлена в методе с типом Boolean, а в строке кода, которая дает ошибку, ей присваивается значение int. Далее мы смотрим как используется эта переменная. Мы не будем доверять нашей внимательности, а пройдемся поиском по коду. Ищем вхождения слова "hasRotate" начиная со строки с ошибкой. Вхождение лишь одно и оно совсем рядом с ошибочной строкой. Это еще одна удача: изменения, которые сделаем будут локальны. Смотрим, как используется переменная: с ее помощью производится проверка на наличие вхождений слова rotate. В принципе, в таких случаях есть два пути: либо создание новой переменной и дальнейшая работа уже с ней, либо изменение кода таким образом, чтобы переменная использовалась правильно. К первому решению чаще прибегают в случае, если изменение логики дальнейшей проверки нежелательно или затруднительно. Поскольку способ решения выбирается исходя из критерия минимального внесения изменений и у нас нет показаний за первое решение, поэтому прибегнем ко второму. Продублируем две строки, начиная со строки с ошибкой, одну пару закомментируем и пока оставим на память. Вторую строку изменим. Вот, что у меня получилось: Код AS3:
Обратите внимание на еще один момент: хотя логика вполне прозрачна, и мы могли бы вместо > -1 использовать, например != -1 или придумать что-нибудь еще, мы оставляем использованный автором способ, поскольку остальной код пока для нас - потемки и абсолютно быть уверенным в правоте своих действий невозможно. По этой же причине мы оставляем закомментированные строки с исходным кодом. На этом процедура исправления ошибки заканчивается, и мы можем вернуться к рефакторингу. |
Продолжаем рефакторинг класса PathToArray
Вложений: 1
Для того, чтобы решить что делать дальше, откройте в FDT панель Problems и отсортируйте по столбцу Resource.
Здесь мы видим два типа ошибок: Local Variable is never used (локальная переменная никогда не используется) Untyped member access (нетипизированный доступ к членам) Первая ошибка встречается лишь однажды: двойным кликом по соответствующей строке в панели Problems переходим на строку с ошибкой в коде и удаляем ее. В итоге у нас останутся только ошибки нетипизированного доступа. Просмотрев код видим, что ошибки вызваны нетипизированными обращениями к переменным объектов fill, stroke, firstP, lastP, lastC. Первые два объявлены как переменные класса, остальные как локальные переменные метода makeDrawCmds. Начнем с локальных переменных. Судя по их использованию в коде, они могут быть типизированы как Point. Попробуем заменить тип на Point в их объявлении. После замены типов и сохранения документа количество ошибок резко уменьшилось и изменился их тип на "You can not assign an 'Object' to a 'Point'". Заменим, вот пример замены: Было: Код AS3:
Код AS3:
Затем, кликая по образовавшимся у скроллера красным меткам ошибок, я переходил на очередную строку с ошибкой и заменял закрывающую фигурную скобку на обычную и удалял "y:". В итоге, количество ошибок типизации в классе снизилось до шести. Все они - обращения к переменным объектов fill и stroke. В отличие от предыдущего шага, у этих объектов отсутствует соответствие в родных классах Flash и нам потребуется создать собственные. Сразу за самой последней закрывающей фигурной скобкой документа создадим внутренние классы. Затем зададим им публичные поля, одноименные с теми, доступ к которым осуществляется из объектов fill и stroke. Добавим в конструкторы соответствующие аргументы. Вот, что у меня получилось: Код AS3:
Код AS3:
Здесь способ ускорения процесса правки несколько иной. Перейдя на начало документа, используем сочетание клавиш CTRL+1. FDT - умница, он хоть и не может решить всех проблем, но он предлагает добавить кастинг соответствующего типа. Соглашаемся, после чего добавляем ключевое слово new и удаляем имена полей и фигурные скобки. было: Код AS3:
Код AS3:
Код AS3:
Закончив, выделим двойным кликом Fill проверяем, везде ли расставлено ключевое слово new. То же самое проделываем со Stroke. В результате наших действий, в классе PathToArray не должно остаться ни одной подсвечивающейся ошибки. Тестируем, убеждаемся в том, что Flash-компилятор не ругается, а изображение рисуется. Итоговый класс аттачу к этому посту. |
Небольшое отступление
Возможно, читатель думает, что я портировал проект, отрефакторил и оптимизировал и теперь, зная все подводные камни, веду их по спланированному сюжету.
Это не так. Я не знаю, к чему мы придем. Я вначале делаю небольшой шаг, затем описываю что сделал. Всё по-честному. - Что же мне дает уверенность в том, что в итоге всё получится хорошо? - Ну, помимо природной наглости, я понимаю, что так или иначе удастся справиться с возникающими вопросами, а также я придерживаюсь простых правил, которые мне помогут:
|
Приводим в порядок Math2
При поверхностной оценке здесь, в отличие от предыдущих классов, может возникнуть необходимость изменения типа данных аргументов.
Следовательно, этот шаг затронет не только текущий класс, но и другие, его использующие. Этот шаг мы оставим на самый последний момент. Сейчас мы нацелены на снижение количества подсвечиваемых ошибок и первое, что стоит сделать - заняться локальными переменными. Идем сверху вниз. ratioTo - пропускаем intersect2Lines - задаем всем аргументам тип Object - задаем всем локальным переменным тип Number rotation - задаем всем аргументам тип Number midPt - задаем аргументам тип Number - указываем возвращаемое значение Object getQuadBez_RP - здесь не указаны типы аргументов, для того, чтобы выяснить поиском по файлам (CTRL+H, File Search) ищем вызовы этого метода и, констатируя факт, указываем использованные типы: Код AS3:
- задаем тип Number переменным dx и dy - остальным переменным задаем тип Object bezierSplit - задаем всем аргументам тип Number - задаем переменной m тип Function pointOnCurve - задаем всем аргументам тип Number - видим, что можем легко избавиться от подсвечивания ошибки нетипизированного доступа и заменяем процесс создания объекта на такой: Код AS3:
- задаем всем аргументам тип Number pointsOnLine - задаем всем аргументам тип Number - задаем переменной i тип int - задаем всем остальным переменным тип Number curveApproxLen - задаем всем аргументам тип Number; Первая часть на этом заканчивается, причем на мажорной ноте: если вы обратите внимание на панель Problems, то увидите, что число ошибок стало меньше 100 и это означает, что окончание процесса не за горами. |
удаляем Object
Следующим нашим шагом борьбы с нетипизированным доступом станет повсеместное удаление типа Object.
Изучив код, увидим, что очень часто Object используется там, где бы мог использоваться Point. Порядок замены в таких случаях:
|
Взглянув на код, определяем какие методы нам проще всего изменить. Внешним признаком будет служить нам то, что в качестве аргументов и возвращаемого значения не применяется тип Object.
Единственный подходящий метод - curveApproxLen. Добавим в методе создание новой точки, назовем ее middle: Код AS3:
Тут же видим: вызываемый метод pointOnCurve подходит для следующего шага, поскольку у него только возвращаемое значение имеет тип Object. Заменяем объект и возвращаемый тип на Point: Код AS3:
Для того, чтобы найти места в коде, где используется метод pointOnCurve применим хитрость: переименуем pointOnCurve в pointOnCurve1, сохраним классс и по списку ошибок увидим где это. Результат - два вызова в этом же классе. Проще всего исправить curveApproxLen. Мы просто ставим вызов метода pointOnCurve вместо new Point(...) и удаляем строку объявления объекта mp. Также можем удалить "Math2." в вызове. В итоге должны получить вот такой метод: Код AS3:
Теперь можно разобраться со вторым методом, использующим pointOnCurve, это метод pointsOnCurve. Сейчас он возвращает массив точек и... драма! Неприятная неожиданность: мы обнаруживаем, что это не могут быть объекты класса Point, поскольку в этом методе задается точке загадочная переменная r. Ну, что-ж, это не критично. Cоздадим подкласс объекта Point. Пока мы не знаем как его называть, поскольку сущность переменной r нам неизвестна. Мы только видим, что это угол, но угол чего именно на этом этапе мы выяснять не имеем права. Второй вопрос - нам следует выяснить будут ли использованы эти точки вне класса Math2, чтобы определиться делать ли новый класс публичным или достаточно будет internal. Для этого переименуем pointsOnCurve в pointsOnCurve1, сохраним документ и пройдемся по полученым ошибкам - местам вызова этого метода. К моему удивлению - ни одной ошибки. Включаем занудство и используя поиск по файлам ищем вхождение строки "pointsOnCurve". Опять ничего кроме его самого. Делаем вывод: мы нашли неиспользуемый метод. Тревога отменяется, закомментируем этот метод полностью и идем дальше. Среди оставшихся четырех методов содержащих ошибки только bezierSplit в качестве параметров не требует Object, поэтому заглянем в него. Этот метод использует другой метод из этого же класса: midPt. Переходим на него и видим, что возвращаемый тип можно заменить на Point, что и делаем. По прежней схеме с переименованием проверим, а не используется ли он где-то еще. Но в этот раз заодно зададим имя без сокращений: midpoint. А поскольку снаружи текущего класса этот метод нигде не используется, то сделаем его приватным. В итоге, он должен выглядеть так: Код AS3:
Ошибок нет, можно продолжить доработку метода bezierSplit. Заменим вызовы метода midpoint через локальную переменную m на обычный вызов и удалим строку объявления этой переменной. Это требуется для того, чтобы убедиться в правильности передаваемых аргументов. Далее, всем объектам, которым присваивается возвращаемое методом midpoint значение зададим тип Point. Но стоит ли на этом останавливаться? Если мы оставим тип Object переменным p1 и p2, то в возвращаемом объекте будет каша. Да, мы не знаем и пока не хотим знать логику приложения, но здравый смысл подсказывает, что p1 и p2 должны иметь такой-же как и у остальных тип - Point. Прислушаемся к голосу разума, исправим, и получим вот такой метод: Код AS3:
Ошибок нет, идем дальше. Я взялся за ratioTo, но переименовав его обнаружил, что он также нигде не используется. Проверил поиском - подтвердилось. Закомментируем и идем дальше. |
Выбирая из оставшихся двух переименованием, обнаруживаем, что метод intersect2Lines используется только в этом классе и лишь в одном месте. Делаем метод приватным.
Наиболее предсказуемые последствия возникнут если сейчас изменить возвращаемый тип. Логика подсказывает, что пересечение двух линий - точка. Заменяем возвращаемый тип на Point. Редактор тут же подсвечивает ошибки: возврат NaN и Object. В первом случае заменяем на null, во втором случае на new Point(...) - в трех местах. После этого двойным кликом выделяем имя метода и используя CTRL+R открываем панель результатов поиска и там переходим на метод getgetQuadBez_RP и заменяем тип переменной s на Point. (Можно заодно удалить Math2. в вызове метода intersect2Lines) Далее приведем в порядок аргументы метода intersect2Lines, задав им тип Point. После этого в панели Problems отсортируем список по полю Description так, чтобы вверху оказались строки "You can not assign an 'Object' to an 'Point'" и приступим к исправлению этих ошибок. Здесь нужно четко понимать: наша задача в данный момент - исправить только эти ошибки, а не весь проект. Наши исправления должны носить по возможности локальный характер. Поэтому поступим следующим образом: - переименуем первый аргумент в point1; - перед вызовом intersect2Lines объявляем переменную p1 с типом Point и присваиваем ей новый объект Point, которому в качестве параметров передаем point1.x и point1.y; - поступаем аналогично со следующим аргументом, задав ему имя control1. - аналогично изменяем оставшиеся два аргумента, имеющие тип Object. - сохраняем проект, тестируем. Вот что имеем на данный момент: Код AS3:
Мы имеем возможность быстро протестировать изменения и, если придется откатиться, то совсем недалеко. Это огромное преимущество маленьких шагов. Итак, в настоящий момент все ошибки типизации сконцентрировались в одном методе: getQuadBez_RP и разбиты на две группы: ошибки вызванные неверной типизацией аргументов и ошибки вызванные объектом halves. Следуя нашей стратегии вначале исправим ошибки, затрагивающие только методы текущего класса. Объект halves - это возвращаемый методом bezierSplit объект, содержащий в себе два других объекта b0 и b1, каждый из которых содержит в себе по 4 объекта типа Point. У нас есть выбор: либо создать пользовательский класс для типизации объекта halves, либо, поскольку его структура жестко задана, использовать массив и последующую типизацию. В данном случае массив предпочтительнее, поскольку это более короткий и логичный путь. Название метода (bezierSplit - разделить безье) и использованное имя переменной, которой присваивается возвращаемое значение (halves - половинки), также говорят за использование массива. Ок, пойдем этим путем. Проверим вначале на какие другие методы окажут воздействие изменения в bezierSplit. Сделать это можно либо переименовав его, либо с помощью CTRL+R. Метод используется только в getQuadBez_RP. Это заодно нам позволяет сделать его приватным. Далее: - изменяем возвращаемый методом bezierSplit тип на Array; - изменяем строку return на: Код AS3:
- изменяем присвоение переменным b0 и b1 на доступ к 0 и 1 элементам массива соответственно; - тестируем. Аналогичным образом поступаем с объектами в возвращаемом методом bezierSplit массиве - также делаем из них массивы: Код AS3:
- чтобы увидеть все вхождения b0 и b1, а также сделать код более читабельным, переименовываем эти переменные в bezier0 и bezier1 соответственно. - дублируем строки, в которых появились ошибки, одну пару комментируем, чтобы не забыть что было изначально; - в тех местах где подсветились ошибки заменяем имена переменных и доступ к их содержимому на оператор доступа к массиву. Замена производится соответственно: ".p1" на "[0]", ".c1" на "[1]", ".c2" на "[2]", ".p2" на "[3]". Наример, было: b0.c1 стало: bezier0[1]; Результат должен получиться таким: Код AS3:
|
вторая часть рефакторинга getQuadBez_RP
Эту часть процесса мы будем делать исходя из очень простой логики действий: вначале вызываем ошибку, затем исправляем.
Изменяем первый тип аргумента метода getQuadBez_RP на тип Point. Сохраняем документ. В панели Properties появляется список ошибок вида: "You can not assign an 'Object' to an 'Point'" Двойным кликом переходим на первую ошибку и заменяем первый аргумент в вызове метода Math2.getQuadBez_RP с объекта на new Point(...). Сохраняем документ, чтобы обновилась панель Problems. Переходим к следующей ошибке этого типа. Исправляем таким образом все ошибки данного типа. Обязательно тестируем. Результатом этого процесса должно стать полное отсутствие ошибок в панели Problems. Это и было целью первого этапа рефакторинга. Мы ее добились и на этом этап можно считать законченным. |
В процессе вы познакомились с различными приемами и принципами решения проблем.
Я и дальше постараюсь применять разные подходы к исправлению схожих задач, чтобы максимально охватить их спектр, даже, если в каком-то конкретном случае решение будет не самым оптимальным. В дальнейшем вы сами владея этими инстурументами сможете выбирать подходящий. Подведем некоторые итоги. Мы добивались того, чтобы в FDT не показывались ошибки. Но мы не прятали их, а именно исправляли. Результатом этого стало то, что кроме коллекции цветов в проекте не осталось объектов типа Object, а это - один из критериев оценки. - Но устраивает ли нас код в текущем состоянии? - Ни в коем случае. Этот код нам непонятен, его повторное использование практически невозможно, внесение изменений в код крайне затруднительно. Собственно именно поэтому мы и говорим об окончании первой волны рефакторинга, но это - только начало. |
Удаление ненужных методов
Вложений: 1
Перед началом следующего этапа почистим код: нужно отыскать и удалить неиспользуемые методы.
Раньше этого не стоило делать: мы пока не можем быть уверены в том, что в других частях проекта эти методы не использованы (имеется ввиду вторая часть - анимация отрисовки svg файла), а возвращаться в последствии к этапу приведения типов не хочется. Удалив лишнее нам проще будет сконцентрироваться на оставшихся методах и сэкономит время. При этом, разумеется, у нас должна остаться копия проекта в текущем состоянии для того, чтобы при необходимости мы могли восстановить любой удаленный метод. Неиспользуемые методы я рекомендую именно удалить, а не просто закомментировать, поскольку отстутствие лишнего кода, в том числе и закомментированного, ускорит процесс нашей работы. Мы уже удаляли методы, так что я не буду концентрироваться на самой процедуре, отмечу лишь тот момент, что удаление одного метода может повлечь за собой прекращение необходимости в другом. Также напоминаю, что удаление метода в незнакомом проекте требует обязательного последующего тестирования. В итоге ненужные методы оказались только в классе Math2 и там, после удаления осталось их всего 4: getQuadBez_RP - публичный, я его переместил в начало класса; intersect2Lines - приватный; midpoint - приватный; bezierSplit - приватный; Чтобы "сверить наши часы" я выкладываю файлы проекта в текущем состоянии. |
Определение следующей цели
Давайте определимся, с тем, чего мы будем добиваться на этом этапе и критерях его окончания.
Тот факт, что редактор не показывает нам ошибки нетипизированного доступа вовсе не означает, что в коде их нет. Неявный нетипизированный доступ - это применение наборов переменных представляющих одну сущность вместо применения объекта такого типа, который отражает сущность. К примеру, точку на плоскости в координатах x=10, y=15 можно представить разными способами: Код AS3:
Пройдемся по классам и отметим для себя такие случаи: Класс SVGDisplayInFlash метод getShapes: - массивы хранят данные, а не объекты, представляющие сущности. Класс Math2 метод getQuadBez_RP: - в аргументах передаются точки вместо кривой Безье 3го порядка; - внутри метода имеются обращения к массивам данных вместо обращений к сущностям. метод intersect2Lines: - в аргументах передаются точки вместо отрезков; метод midpoint: - в аргументах передаются координаты вместо отрезка; метод bezierSplit: - в аргументах передаются координаты вместо кривой Безье 3го порядка; - возвращаемое значение содержит массивы данных вместо кривых Безье 3го порядка. Класс PathToArray метод makeDrawCmds: - в аргументах и в теле метода широко используются массивы данных вместо классов, представляющих сущности. Класс String2 случаев скрытого нетипизированного доступа нет. Итак, наша задача на следующем шаге - избавиться от случаев скрытого нетипизированного доступа. |
Следуя нашим правилам, начинаем с методов, правка которых вызовет наименьшее влияние на проект.
Этип критериям лучше всего подходит приватный метод midpoint: у него правильно типизированное возвращаемое значение, а поскольку он приватный, то аргументы для него будут изготовлены в этом же классе. Методология та-же, что и применятась ранее: вызываем ошибку и исправляем ее. Продублируем и закомментируем строку объявления метода. На память. Заменим все аргументы метода на один (line:LineSVG). Разумеется, такого типа не существует и, поэтому редактор подсветит ошибки. Выделяем LineSVG и комбинацией CTRL+1 вызываем quick fix, соглашаемся на предложение создать класс LineSVG. Сохраняем его, возвращаемся. Честно говоря, глядя на код хочется пойти по более короткому, хотя и теоретически опасному пути: немедленно перенести метод midpoint в класс LineSVG, поскольку он явно завистлив к данным этого класса и не использует данных текущего класса. Ну, что-ж, сократим путь. Переносим вместе с комментариями. Теперь, собственно, нужно определиться с тем, что будет из себя представлять этот класс и как будет создаваться. Задаем конструктор, метод инициализации экземпляра класса и get set методы доступа к стартовой и конечной точкам отрезка. После этого делаем метод midpoint публичным и не статическим и реализуем аналогичное поведение, но с использованием стандартных методов. Заодно исправим комментарии к методу. В итоге получаем такой класс: Код AS3:
Теперь можно приступить к исправлению образовавшихся ошибок в методе bezierSplit. |
Исправим все строки, в которых применяется midpoint. Поступим следующим образом:
- продублируем и закомментируем верхнюю; - удалим ошибочный вызов метода; - вместо него впишем создание объекта LineSVG с пустыми new Point(); - из верхней строки скопируем пары координат и соответственно вставим в new Point() - добавим вызов метода midpoint() Результат должен получиться таким: Код AS3:
В итоге получаем отсутствие ошибок, а это значит, что мы можем протестировать плоды наших усилий. Ошибок нет, переходим к типизации возвращаемого значения и аргументов. |
создаем класс CubicBezierSVG
В качестве аргументов методу bezierSplit передается четыре пары координат x и y, описывающих одну сущность - кривую Безье третьего порядка.
Чтобы описать эту сущность, создаем класс, в котором описываем базовые свойства кривой: Код AS3:
Для этого просто скопируем метод в класс, продублируем и закомментируем верхний - на память. Переименовываем метод в split, и делаем его публичным и не статическим. Удаляем аргументы. Редактор расцвел массой ошибок. Исправим их, заменив создание объектов Point из координат на соответствующие объекты Point текущего класса. Для этого подглядываем в аргументы закомментированного класса: там точки идут по парам представляя контрольные точки кривой Безье. Точки заменяем в том порядке, в котором они идут в аргументах закомментированного метода bezierSplit. В итоге избавляемся от всех ошибок и видим, что можем удалить объявление точек p1 и p2 и заменить их непосредственно на startPoint и endPoint. Для этого копируем startPoint в буфер обмена, удаляем строку инициализации p1, и там, где подсветилась ошибка вставляем из буфера обмена startPoint. Затем делаем то-же самое с p2. Заменяем вложенные массивы на создание объектов CubicBezierSVG. После чего можем удалить закомментированный метод. Затем мы можем заменить все new Point(...) на ранее полученные точки. Полученный результат должен быть таким: Код AS3:
Код AS3:
|
Тестирование нового метода
Прежде чем заменять вызовы старого метода на новый, мы обязательно(!!!) должны протестировать наш новый метод split.
Это ключевой и, как вы убедитесь, совсем не лишний шаг в выбранном способе рефакторинга. Создавая новый метод мы имеем право делать всё что нам заблагорассудится с новым кодом, но старый код не имеем права трогать. И, когда новый метод готов, мы тестируем его. Тестирование нового метода обязательно производится заменой всей логики старого метода на вызов нового метода. Продублируем метод Math2.bezierSplit и закомментируем верхний, чтобы было легко откатиться в случае ошибки. После чего удалим всю старую логику и заменим на новую: Код AS3:
|
Задержка по пути
Оказывается рановато заменять старые вызовы на новые.
Взглянув на метод Math2.getQuadBez_RP видим, что предварительно стоит заняться удалением локальных переменных p1, c1, c2, p2, которые дублируют точки, передаваемые в аргументах. Для этого комментируем строку инициализации переменной и заменяем подсвечивающиеся ошибки на соответствующий аргумент. После чего удаляем ненужные закомментированные строки и получаем в итоге вот такой метод: Код AS3:
|
И вот, наконец можем заменить вызов метода Math2.bezierSplit на метод split класса CubicBezierSVG.
Дублируем весь код, находящийся внутри блока if (до строки с else) и закомментируем верхний. Чтобы использовать метод split требуется экземпляр объекта CubicBezierSVG. Создаем его, заменяем метод со старого на новый и задаем новый тип переменным bezier0 и bezier1: Код AS3:
Переименуем переменную bezier0 на firstHalf. Редактор кода подсвечивает ошибки и мы идем по ним, заменяя имя и обращение к точке, к которой осуществляется доступ. Проделываем то-же самое с переменной bezier1, заменив ее имя на secondHalf. Получаем в итоге: Код AS3:
|
Рефакторинг intersect2Lines
Я напомню цель текущего шага: удаление случаев скрытого нетипизированного доступа.
Следующей мишенью наших действий станет метод Math2.intersect2Lines. Этот случай очень похож на предыдущий. Но, как я и обещал, чтобы разнообразить наш инструментарий способ рефакторинга будет выбран иной. Вырежем и вставим метод в класс LineSVG, сделаем его публичным. Сохраним оба документа (CTRL+SHIFT+S). Используя панель Problems перейдем на строку с ошибкой и добавим "LineSVG." перед вызовом метода. Поскольку мы намерены избавиться от скрытого нетипизированного доступа в аргументах метода, для начала снизим зависимость кода метода от аргументов. В самом начале метода объявим два объекта first и second типа LineSVG и создадим их используя аргументы метода. Везде далее в коде заменим использование аргументов на доступ через объекты LineSVG. Теперь аргументы используются только для создания объектов LineSVG, чего мы и добивались. Сделаем метод не статическим: удаляем static из объявления метода. Сохраняем документ, переходим на ошибку используя панель Problems. Создаем экземпляр LineSVG и от его имени вызываем метод intersect2Lines: Код AS3:
Заменим аргументы на один: second : LineSVG и удалим в теле метода объявление second. Перейдем на ошибки в методе getQuadBez_RP. Скопируем строку объявления startArm, переименуем переменную в endArm и скопируем параметры из вызова метода intersect2Lines. После этого параметры заменим на endArm. В результате наших действий имя метода перестало отражать его суть. Переименуем в getLineIntersection и исправим вызов метода. В итоге, измененная чать метода Math2.getQuadBez_RP будет выглядеть так: Код AS3:
Удалим объявления временных переменных ссылающихся на существующие значения.Подробно процесс на примере: - закомментируем строку var x1 : Number = start.x; - скопируем start.x в буфер обмена; - выделим первую ошибку: обращение к x1; - CTRL+F, ставим фокус в поле Replace With, CTRL+V, в нем должна появиться строка "start.x"; - отмечаем чек боксы Case Sensitive и Whole Word - жмем Replace All Впрочем, вам возможно будет удобнее заменить выделением и вставкой. Далее также поступаем с переменными y1, x4, y4 и получаем такой код: Код AS3:
Заменяем dx1 на currentDistanceX, а dx2 на targetDistanceX, исправляя подсвечивающиеся ошибки сразу после каждого переименования. Пробуем поставить const вместо var. Ошибок нет, так и оставим. Идем дальше вниз по коду. Добавляем фигурные скобки блоку if. Думайте что хотите, но я уверен, что if без фигурных скобок снижает читабельность кода, а пользы никакой. В вычислении переменных m1 и m2 видим, что используются вычисления, аналогичные currentDistanceX и targetDistanceX. Объявим аналогичные локальные константы currentDistanceY и targetDistanceY и присвоим им соответствующие значения скопировав из вычислений. Затем заменим вычисления на эти константы: Код AS3:
Код AS3:
В итоге имеем вот такой метод: Код AS3:
Потому, что программировать не надо бояться, программировать радоваться надо. А если серьезно, то обратите внимание на то, что мы уже довольно давно работаем с кодом, но до сих пор так и не вникали в суть его логики. Более того, если вы обратили внимание, в процессе рефакторинга код понемногу сам нам рассказывает о себе. |
рефакторим getQuadBez_RP
Вернемся в класс Math2.
Здесь мы видим сиротливо приютившийся метод getQuadBez_RP и мы понимаем, что из-за его завистливости к данным других объектов и вселенского одиночества в классе, он обречен на перемещение, а класс будет удален. Поскольку в первую очередь мы намерены избавить метод от скрытого нетипизированного доступа в аргументах, то видим, что заменяться они будут на объект CubicBezierSVG. Туда и будем перемещать. Правда, мы видим, что метод также использует класс LineSVG и, вполне возможно, что он будет завистлив к данным этого класса. Но это не проблема. Мы впоследствии сможем переместить, если потребуется. Итак, приступим. Перемещаем метод вместе с комментариями в класс CubicBezierSVG, сохраняем все документы и идем исправлять образовавшиеся ошибки в окне Problems. Не буду расписывать - вы и сами знаете что делать. Тестируем. Затем удаляем класс Math2, при этом мысленно благодарим его за труд, который он делал в течение своей короткой, но очень яркой и полезной жизни. Возвращаемся к методу getQuadBez_RP. Как и в предыдущем случае, нам вначале потребуется избавиться от зависимости кода от аргументов метода, которые мы будем заменять на объект CubicBezierSVG. Для этого вначале метода создаем экземпляр класса CubicBezierSVG: Код AS3:
Чтобы гарантировать себя от невнимательности, действуем по простой схеме: - переименовываем аргумент; - новое имя используем только в инициализации объекта source; - остальные обращения заменяем на доступ через объект source. Вот что в итоге получилось: Код AS3:
Удаляем ключевое слово static, сохраняемся и идем исправлять ошибки. Принцип, как всегда прост: - вначале метода объявляем переменную: Код AS3:
Код AS3:
- заменяем обращение к статическому методу на обращение через экземпляр source. - переходим к следующей ошибке, пока они не иссякнут. - тестируем проект. - не забываем радоваться тому, как у нас ловко всё получается. |
А теперь можно заняться удалением теперь уже ненужных аргументов.
Заменим создание объекта source на присвоение ему this: Код AS3:
До тех пор, пока использовались аргументы, в методе создавались новые объекты CubicBezierSVG. А как только перешли на использование this наша невнимательность выползла наружу: мы не добавили объекты, от имени которых рекурсивно вызывается метод getQuadBez_RP. Исправим. Для начала откатимся назад до рабочего состояния: Код AS3:
Протеституем, убедимся, что всё работает. Вот участок кода, который требует вмешательства: Код AS3:
В этот момент мы должны остановиться и сказать себе: это не рефакторинг. Это действия по изменению логики приложения. К таким действиям мы должны подходить с совсем другими правилами. |
Оценка рисков изменения логики
Рефакторинг как процесс - ряд мелких эквивалентных шагов не изменяющих логику приложения. Если мы хорошо понимаем эквивалентность каждого конкретного шага, то достаточно общего тестирования проекта.
Ситуация резко изменяется в тот момент, когда мы вносим изменения в логику. Тем более, если изменения вносятся в чужой код. В этом случае оценка рисков и тестирование должны быть куда более глубокими. Вернемся к коду и оценим риски. Использование объекта sourceBezier очень кратко и я вижу только одну потенциальную проблему: метод split может изменять объект sourceBezier, что приведет к непредсказуемым последствиям в дальнейшем. Мы должны убедиться в обратном. Перейдем в метод split и оценим возможность неприятных последствий. В коде метода split отсутствуют действия, изменяющие текущий объект. Но в качестве параметров создания объектов Line передаются точки текущего объекта и существует опасность, что они могут быть изменены методом midpoint. Переходим на метод midpoint и видим, что с точками не производится изменяющих их операций, поскольку метод Point.interpolate не изменяет аргументы. Но тут возникает вопрос: а есть ли смысл в методе split создавать объект LineSVG только для того, чтобы в итоге вызвать метод совсем другого класса - Point? Похоже, что нет, ведь объекты LineSVG в методе split ни для чего более не используются. Решено, перед дальнейшими операциями делаем небольшой рефакторинг по замене метода midpoint на вызов interpolate и получаем: Код AS3:
Раз уж мы больше в этом методе не применяем midpoint, то проверим, может быть больше он не нужен никому? Проверяем, убеждаемся в ненужности, удаляем, тестируем. Еще раз вернемся к методу split. Дело в том, что здесь я заметил одну нехорошую особенность: при создании возвращаемых кривых, в качестве параметров передаются startPoint и endPoint текущего объекта. Это значит, что если изменится стартовая точка первой возвращаемой кривой или конечная точка второй, то изменится текущая кривая. Поскольку мы не знаем, что дальше будет происходить с возвращаемыми кривыми, то желательно застраховаться от таких неприятностей, передавая не точки кривой, а ее клоны. Но можем ли мы сделать это прямо сейчас? Достаточно ли у нас информации и возможностей? Ведь не исключено, что это запланированное поведение метода и так и должно быть. Что-то мне подсказывает, что, возможно, это мы внесли такое поведение в код. Для того, чтобы проверить, открываю исходный проект, класс Math2, метод bezierSplit. Так и есть - это моя ошибка. В старом методе нет и намека на то, что должен передаваться существующий объект. Рыдаю, заламываю руки, посыпаю голову пеплом. Нет, не то, боюсь это не поможет. Исправляем ошибку, используя клоны вместо самих точек: Код AS3:
Раз уж такое произошло однажды, значит, где-то в коде имеется аналогичная проблема. Проверяем методы классов CubicBezierSVG и LineSVG на предмет аналогичных ошибок. Таких нет, идем дальше. Вновь возвращаемся к тому, с чего начали: к оценке рисков изменения логики. |
Оценка рисков изменения логики. Попытка номер 2
Краткое содержание предыдущей серии:
мы хотим вместо sourceBezier использовать source и оцениваем риск того, что метод split может изменять текущий объект. Итак, благодаря нашим действиям стало очевидно, что split этого не делает. Заменяем. Тестируем. Конечно, если бы возникли малейшие сомнения, то процесс был бы куда серьезней, да и тестирование потребовало бы больших трудозатрат. В очевидной же ситуации занудствовать не стоит, и мы идем дальше. Удаляем создание объекта sourceBezier и заменяем вхождение на source: Код AS3:
После замены рекурсивные вызовы будут выглядеть так: Код AS3:
Можем вернуться к рефакторингу. |
Всё-таки делаем это!
Как мы помним, наша попытка заменить создание нового объекта на использование this провалилась потому, что рекурсивные методы вызывались не из нужного объекта. После того, как мы заменили передаваемые аргументы на точки одного объекта, нам стало понятно от чьего имени должна происходить рекурсия.
Устанавливаем правильные объекты для рекурсивного метода и заменяем создание нового объекта на this: Код AS3:
Редактор нам подсветил неиспользуемые переменные. Удаляем их, сохраняем документ. Панель Problems насыпала проблем. Проходим по всем ошибочным вызовам, удаляя в каждом первые четыре аргумента. Делаем это до тех пор, пока панель Problems не прекратит истерику. Результатом наших трудов стало то, что метод getQuadBez_RP приведен в такое состояние: Код AS3:
|
Рефакторим объекты массива.
Загадочная буква q в имени массива, а также имена в создаваемом объекте нас наталкивают на мысль о том, что этот объект по-сути - кривая Безье второго порядка.
Чтобы в следующий раз не вспоминать что-же там в массиве, переименуем аргумент qcurves в quadraticCurves и исправим появившиеся ошибки. Можно даже не тестировать после этого. Хотя, если вы не параноик, это вовсе не значит, что за вами никто не следит. Можете протестировать на всякий случай. Создадим класс, определим методы доступа к управляющим точкам, а так-же реализуем методы доступа к данным, одноименных с используемыми в объекте: Код AS3:
Затем перед этими строками инициализируем управляющие точки и кривую безье второго порядка: Код AS3:
|
Если объект создавался, значит, что его кто-то использовал. И это был нетипизированый доступ. Включаем отдел головного мозга, ответственный за Шерлока Холмса и проводим частное расследование. Задача - выяснить кто использует массив, передаваемый в аргументах.
Выделяем имя метода getQuadBez_RP и применяем волшебную комбинацию CTRL+R. В открывшейся панели поиска видим, что метод вызывается в этом же классе (рекурсия) и в классе PathToArray в методе makeDrawCmds. Используя панель Search переходим на makeDrawCmds, сразу попадаем на первое вхождение использования метода. Немедленно сильно пугаемся кода: огромный метод, непонятные имена переменных, использование в case загадочных строковых данных, if-ы и while-ы непонятной глубины вложенности. И нам всё это придется разгрести. Видим, что всего вхождений четыре. Видим также, что переменная, хранящая ссылку на наш массив, здесь называется qc. Перейдем на объявление массива, видим, что это локальная переменная. Для начала поиском и заменой переименуем ее в quadraticCurves, чтобы появилась визуальная связь с массивом в методе getQuadBez_RP - ведь это один и тот-же массив. Заодно пора переименовать и сам метод getQuadBez_RP. Назовем его без сокращений и постараемся в имени отразить его деятельность. Переименовываем в toQuadraticBezierArray. Полученная строка source.toQuadraticBezierArray тоже не очень понятна, поэтому переименуем переменную source в сubicBezier. Теперь, если кто-то увидит строку кода "сubicBezier.toQuadraticBezierArray(...", то ему будет намного понятнее что происходит. |
Краткое отступление: вы не думайте, что код, написанный Helen плох. Для своего времени это прекрасный код.
Просто в то время цели, которых добивались от кода, были совсем другими. Никто даже не думал о том, чтобы код был простым и читабельным. Более того, лучшим считался тот программист, который был способен написать много непонятного кода и, при этом, умудриться самому в нем не запутаться. Еще один очень вероятный фактор: Helen привела к такому виду код в процессе оптимизации, а мы сейчас идем в обратном направлении. К тому-же, попробуйте написать проект во Flash IDE и поймете, что первопроходцы Flash тех времен - герои. Но так или иначе, в результате с тех доисторических времен до нас дошло много кода, но никто не знает что и как он делает. Сейчас ситуация в корне изменилась: не назовут программиста хорошим, если не понятно что и как делает его код. Имейте это ввиду. |
extract method
В классе PathToArray мы видим четыре места, в которых используется вызов метода toQuadraticBezierArray.
Внешне код очень похож и, возможно, речь идет об одинаковом коде. Проверим. Первым делом жмем волшебную комбинацию: CTRL+SHIFT+F. Код отформатируется, расставятся пробелы и всё такое. Теперь, если строки одинаковы, то они будут находиться поиском. Начнем со строки Код AS3:
Чтобы после поиска было удобно возвращаться в начало, правым кликом по серому полю окна слева добавьте закладку и затем используйте ее для возврата: слева, рядом со скроллером можно будет кликнуть на нее. Итак, 1 - совпадает 2 - не совпадает 3 - совпадает 4 - не совпадает, хотя вроде должна. Ага, вынесем объявление переменной ii в начало метода. CTRL+SHIFT+F, проверяем, теперь совпадает. После цикла две строки не совпадают и только последняя еще раз совпадает. Это очень хороший результат: мы сможем вынести совпадающие части в отдельный метод, что, возможно, избавит от нас от необходимости вносить изменения в четырех местах. Поэтому временно откладываем рефакторинг скрытого нетипизированного доступа и приступаем и извлечению метода. Опускаем строку присвоения массива под строку создания сubicBezier, затем копируем участок кода. Создаем новый метод и вставляем код туда: Код AS3:
- массив quadraticCurves можем объявить прямо здесь, объявляем; - сubicBezier должны получить, добавляем аргумент; - итератор цикла объявляем здесь же, и переименовываем из двойного ii в одинарную i. Имеем вот такую картину: Код AS3:
Код AS3:
По окончании, редактор нам подскажет, что появились две неиспользованные переменные. Удалим их. Имя метода никуда не годится, назовем его pushCubicBezierDrawCommands. |
Заканчиваем с этим шагом!
Продолжим приводить в порядок наш новый метод.
Организуем типизированный доступ к данным: Код AS3:
Заменяем доступ к свойствам с непонятными именами на доступ через управляющие точки: Код AS3:
Закомментируем все эти свойства. Редактор не видит ошибок. Протестируем. С тем же успехом. Отлично, эти свойства можем удалять. Итогом нашей работы стало то, что удаляя один случай скрытого нетипизированного доступа мы обнаружили еще один. |
Типизируем еще один
Скопируем "drawCmds.push", перейдем в начало документа и пройдемся поиском, чтобы посмотреть, что в принципе добавляется в массив. Видим, что всегда это буква и массив данных. Эта информация в сочетании с именем массива, которое не говорит, а намекает смекалистым на то, что в нем хранятся команды рисования, позволяет нам придумать имя для будущего класса объекта данных. Мы назовем его DrawingCommand.
Создаем класс DrawingCommand. Код AS3:
В таком раскладе мне это категорически не нравится. Вместо массива мы получим объект данных мало чем по своей практичности отличающийся от него. При необходимости им воспользоваться нам придется каждый раз вначале выяснять как мы можем им воспользоваться проверяя его тип. От этого впоследствии нам придется избавляться с помощью замены условных операторов полиморфизмом. Так не проще ли сейчас грамотно реализовать логику, чтобы потом не возвращаться? Ведь эту часть мы делаем с нуля. Так и поступим. Кратко опишу чего мы будем добиваться: мы сделаем для каждой команды рисования свой класс. Чтобы эти классы правильно типизировались и могли иметь общую логику, отнаследуемся от класса DrawingCommand. Чтобы создавать экземпляры этих классов, сделаем DrawingCommand фабрикой. Еще раз вернемся к коду и посмотрим, какие именно классы нам придется создавать. В этом нам очень поможет метод getShapes класса SVGDisplayInFlash, поскольку в нем компактно использованы все варианты применения. Удаляем всё содержимое класса, копируем блок switch из getShapes и закомментируем его. Поскольку у нас будет целая группа классов, то создаем в папке svg папку draw и перемещаем туда класс. Выглядит сейчас он так: Код AS3:
Код AS3:
Чтобы это сделать быстро и качественно, используем File Search. Но перед этим, чтобы ограничить поиск только нужными папками создадим новый workspace с именем SVGToFlashSrc, в который включим только файлы, лежащие в папке src. - выделяем строку "F" (с кавычками); - CTRL+H, выделенная строка должна оказаться в поле ввода - выбираем workspace с именем SVGToFlashSrc; - жмем кнопку Replace... - в поле with вводим: DrawingCommand.FILL - далее шаг за шагом заменяем, однако не делаем этого в вызовах String2.replace(...) потому, что если заменим, то бардак там будет полным. Ибо чует сердце, что единообразие вызовов этих методов нам еще понадобится. |
Общий вид фабричного метода получается таким:
Код AS3:
В фабричный метод приходится передавать различные сущности в одних и тех же параметрах, а это неверно. Поэтому отказываемся и от этого пути, и заходим с другой стороны: просто создаем классы команд с теми аргументами конструкторов, которые обеспечат должный уровень типизации. |
| Часовой пояс GMT +4, время: 10:18. |
Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2026, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.