|
|
|||||
Регистрация: Apr 2001
Адрес: Moscow
Сообщений: 1,475
|
Определение следующей цели
Давайте определимся, с тем, чего мы будем добиваться на этом этапе и критерях его окончания.
Тот факт, что редактор не показывает нам ошибки нетипизированного доступа вовсе не означает, что в коде их нет. Неявный нетипизированный доступ - это применение наборов переменных представляющих одну сущность вместо применения объекта такого типа, который отражает сущность. К примеру, точку на плоскости в координатах x=10, y=15 можно представить разными способами: var point1:Point = new Point(10, 15); var point2:Array = new Array(10, 15); var point3:Object = {x:10, y:15}; Пройдемся по классам и отметим для себя такие случаи: Класс SVGDisplayInFlash метод getShapes: - массивы хранят данные, а не объекты, представляющие сущности. Класс Math2 метод getQuadBez_RP: - в аргументах передаются точки вместо кривой Безье 3го порядка; - внутри метода имеются обращения к массивам данных вместо обращений к сущностям. метод intersect2Lines: - в аргументах передаются точки вместо отрезков; метод midpoint: - в аргументах передаются координаты вместо отрезка; метод bezierSplit: - в аргументах передаются координаты вместо кривой Безье 3го порядка; - возвращаемое значение содержит массивы данных вместо кривых Безье 3го порядка. Класс PathToArray метод makeDrawCmds: - в аргументах и в теле метода широко используются массивы данных вместо классов, представляющих сущности. Класс String2 случаев скрытого нетипизированного доступа нет. Итак, наша задача на следующем шаге - избавиться от случаев скрытого нетипизированного доступа.
__________________
http://realaxy.com Последний раз редактировалось iNils; 20.12.2010 в 13:24. |
|
|||||
Регистрация: Apr 2001
Адрес: Moscow
Сообщений: 1,475
|
Следуя нашим правилам, начинаем с методов, правка которых вызовет наименьшее влияние на проект.
Этип критериям лучше всего подходит приватный метод midpoint: у него правильно типизированное возвращаемое значение, а поскольку он приватный, то аргументы для него будут изготовлены в этом же классе. Методология та-же, что и применятась ранее: вызываем ошибку и исправляем ее. Продублируем и закомментируем строку объявления метода. На память. Заменим все аргументы метода на один (line:LineSVG). Разумеется, такого типа не существует и, поэтому редактор подсветит ошибки. Выделяем LineSVG и комбинацией CTRL+1 вызываем quick fix, соглашаемся на предложение создать класс LineSVG. Сохраняем его, возвращаемся. Честно говоря, глядя на код хочется пойти по более короткому, хотя и теоретически опасному пути: немедленно перенести метод midpoint в класс LineSVG, поскольку он явно завистлив к данным этого класса и не использует данных текущего класса. Ну, что-ж, сократим путь. Переносим вместе с комментариями. Теперь, собственно, нужно определиться с тем, что будет из себя представлять этот класс и как будет создаваться. Задаем конструктор, метод инициализации экземпляра класса и get set методы доступа к стартовой и конечной точкам отрезка. После этого делаем метод midpoint публичным и не статическим и реализуем аналогичное поведение, но с использованием стандартных методов. Заодно исправим комментарии к методу. В итоге получаем такой класс: package com.itechnica.svg { import flash.geom.Point; public class LineSVG { private var startPoint : Point; private var endPoint : Point; public function LineSVG(start:Point, end : Point) { initInstance(start, end); } private function initInstance(start : Point, end : Point) : void { startPoint = start; endPoint = end; } public function get start() : Point { return startPoint; } public function set start(value : Point):void { startPoint = value; } public function get end() : Point { return endPoint; } public function set end(value : Point):void { endPoint = value; } /** * @returns Point the midpoint of current line segment */ public function midpoint():Point { return Point.interpolate(startPoint, endPoint, 0.5); } } } Теперь можно приступить к исправлению образовавшихся ошибок в методе bezierSplit.
__________________
http://realaxy.com Последний раз редактировалось iNils; 20.12.2010 в 13:24. |
|
|||||
Регистрация: Apr 2001
Адрес: Moscow
Сообщений: 1,475
|
Исправим все строки, в которых применяется midpoint. Поступим следующим образом:
- продублируем и закомментируем верхнюю; - удалим ошибочный вызов метода; - вместо него впишем создание объекта LineSVG с пустыми new Point(); - из верхней строки скопируем пары координат и соответственно вставим в new Point() - добавим вызов метода midpoint() Результат должен получиться таким: // var p01:Point = midpoint (p1x, p1y, c1x, c1y); var p01:Point = new LineSVG(new Point(p1x, p1y), new Point(c1x, c1y)).midpoint(); В итоге получаем отсутствие ошибок, а это значит, что мы можем протестировать плоды наших усилий. Ошибок нет, переходим к типизации возвращаемого значения и аргументов.
__________________
http://realaxy.com Последний раз редактировалось iNils; 20.12.2010 в 13:24. |
|
|||||
Регистрация: Apr 2001
Адрес: Moscow
Сообщений: 1,475
|
создаем класс CubicBezierSVG
В качестве аргументов методу bezierSplit передается четыре пары координат x и y, описывающих одну сущность - кривую Безье третьего порядка.
Чтобы описать эту сущность, создаем класс, в котором описываем базовые свойства кривой: package com.itechnica.svg { import flash.geom.Point; public class CubicBezierSVG { private var startPoint : Point; private var startControlPoint : Point; private var endControlPoint : Point; private var endPoint : Point; public function CubicBezierSVG(start:Point, startControl:Point, endControl:Point, end:Point) { initInstance(start, startControl, endControl, end); } private function initInstance(start : Point, startControl : Point, endControl : Point, end : Point) : void { startPoint = start; startControlPoint = startControl; endControlPoint = endControl; endPoint = end; } public function get start() : Point { return startPoint; } public function set start(value : Point):void { startPoint = value; } public function get startControl() : Point { return startControlPoint; } public function set startControl(value : Point):void { startControlPoint = value; } public function get endControl() : Point { return endControlPoint; } public function set endControl(value : Point):void { endControlPoint = value; } public function get end() : Point { return endPoint; } public function set end(value : Point):void { endPoint = value; } } } Для этого просто скопируем метод в класс, продублируем и закомментируем верхний - на память. Переименовываем метод в split, и делаем его публичным и не статическим. Удаляем аргументы. Редактор расцвел массой ошибок. Исправим их, заменив создание объектов Point из координат на соответствующие объекты Point текущего класса. Для этого подглядываем в аргументы закомментированного класса: там точки идут по парам представляя контрольные точки кривой Безье. Точки заменяем в том порядке, в котором они идут в аргументах закомментированного метода bezierSplit. В итоге избавляемся от всех ошибок и видим, что можем удалить объявление точек p1 и p2 и заменить их непосредственно на startPoint и endPoint. Для этого копируем startPoint в буфер обмена, удаляем строку инициализации p1, и там, где подсветилась ошибка вставляем из буфера обмена startPoint. Затем делаем то-же самое с p2. Заменяем вложенные массивы на создание объектов CubicBezierSVG. После чего можем удалить закомментированный метод. Затем мы можем заменить все new Point(...) на ранее полученные точки. Полученный результат должен быть таким: public function split():Array { var p01:Point = new LineSVG(startPoint, startControlPoint).midpoint(); var p12:Point = new LineSVG(startControlPoint, endControlPoint).midpoint(); var p23:Point = new LineSVG(endControlPoint, endPoint).midpoint(); var p02:Point = new LineSVG(p01, p12).midpoint(); var p13:Point = new LineSVG(p12, p23).midpoint(); var p03:Point = new LineSVG(p02, p13).midpoint(); return [ new CubicBezierSVG(startPoint, p01, p02, p03), new CubicBezierSVG(p03, p13, p23, endPoint) ]; } public function split():Array { const startMidpoint:Point = new LineSVG(startPoint, startControlPoint).midpoint(); const middleMidpoint:Point = new LineSVG(startControlPoint, endControlPoint).midpoint(); const endMidpoint:Point = new LineSVG(endControlPoint, endPoint).midpoint(); const startMiddleMidpoint:Point = new LineSVG(startMidpoint, middleMidpoint).midpoint(); const middleEndMidpoint:Point = new LineSVG(middleMidpoint, endMidpoint).midpoint(); const centerMidpoint:Point = new LineSVG(startMiddleMidpoint, middleEndMidpoint).midpoint(); return [ new CubicBezierSVG(startPoint, startMidpoint, startMiddleMidpoint, centerMidpoint), new CubicBezierSVG(centerMidpoint, middleEndMidpoint, endMidpoint, endPoint) ]; }
__________________
http://realaxy.com Последний раз редактировалось iNils; 20.12.2010 в 13:24. |
|
|||||
Регистрация: Apr 2001
Адрес: Moscow
Сообщений: 1,475
|
Тестирование нового метода
Прежде чем заменять вызовы старого метода на новый, мы обязательно(!!!) должны протестировать наш новый метод split.
Это ключевой и, как вы убедитесь, совсем не лишний шаг в выбранном способе рефакторинга. Создавая новый метод мы имеем право делать всё что нам заблагорассудится с новым кодом, но старый код не имеем права трогать. И, когда новый метод готов, мы тестируем его. Тестирование нового метода обязательно производится заменой всей логики старого метода на вызов нового метода. Продублируем метод Math2.bezierSplit и закомментируем верхний, чтобы было легко откатиться в случае ошибки. После чего удалим всю старую логику и заменим на новую: private static function bezierSplit(p1x:Number, p1y:Number, c1x:Number, c1y:Number, c2x:Number, c2y:Number, p2x:Number, p2y:Number):Array { var curve : CubicBezierSVG = new CubicBezierSVG( new Point(p1x, p1y), new Point(c1x, c1y), new Point(c2x, c2y), new Point(p2x, p2y)); var halves:Array = curve.split(); var firstCurve : CubicBezierSVG = halves[0] as CubicBezierSVG; var secondCurve : CubicBezierSVG = halves[1] as CubicBezierSVG; return [ [firstCurve.start, firstCurve.startControl, firstCurve.endControl, firstCurve.end], [secondCurve.start, secondCurve.startControl, secondCurve.endControl, secondCurve.end] ]; }
__________________
http://realaxy.com Последний раз редактировалось iNils; 20.12.2010 в 13:24. |
|
|||||
Регистрация: Apr 2001
Адрес: Moscow
Сообщений: 1,475
|
Задержка по пути
Оказывается рановато заменять старые вызовы на новые.
Взглянув на метод Math2.getQuadBez_RP видим, что предварительно стоит заняться удалением локальных переменных p1, c1, c2, p2, которые дублируют точки, передаваемые в аргументах. Для этого комментируем строку инициализации переменной и заменяем подсвечивающиеся ошибки на соответствующий аргумент. После чего удаляем ненужные закомментированные строки и получаем в итоге вот такой метод: public static function getQuadBez_RP(point1:Point, control1:Point, control2:Point, point2:Point, k:Number, qcurves:Array):void { // find intersection between bezier arms var s:Point = intersect2Lines (point1, control1, control2, point2); // find distance between the midpoints var dx:Number = (point1.x + point2.x + s.x * 4 - (control1.x + control2.x) * 3) * .125; var dy:Number = (point1.y + point2.y + s.y * 4 - (control1.y + control2.y) * 3) * .125; // split curve if the quadratic isn't close enough if (dx*dx + dy*dy > k) { var halves:Array = bezierSplit (point1.x, point1.y, control1.x, control1.y, control2.x, control2.y, point2.x, point2.y); var bezier0:Array = halves[0]; var bezier1:Array = halves[1]; // recursive call to subdivide curve getQuadBez_RP (point1, bezier0[1], bezier0[2], bezier0[3], k, qcurves); getQuadBez_RP(bezier1[0], bezier1[1], bezier1[2], point2, k, qcurves); } else { // end recursion by saving points qcurves.push({p1x:point1.x, p1y:point1.y, cx:s.x, cy:s.y, p2x:point2.x, p2y:point2.y}); } }
__________________
http://realaxy.com Последний раз редактировалось iNils; 20.12.2010 в 13:25. |
|
|||||
Регистрация: Apr 2001
Адрес: Moscow
Сообщений: 1,475
|
И вот, наконец можем заменить вызов метода Math2.bezierSplit на метод split класса CubicBezierSVG.
Дублируем весь код, находящийся внутри блока if (до строки с else) и закомментируем верхний. Чтобы использовать метод split требуется экземпляр объекта CubicBezierSVG. Создаем его, заменяем метод со старого на новый и задаем новый тип переменным bezier0 и bezier1: var sourceBezier : CubicBezierSVG = new CubicBezierSVG(point1, control1, control2, point2); var halves:Array = sourceBezier.split(); var bezier0:CubicBezierSVG = halves[0] as CubicBezierSVG; var bezier1:CubicBezierSVG = halves[1] as CubicBezierSVG; Переименуем переменную bezier0 на firstHalf. Редактор кода подсвечивает ошибки и мы идем по ним, заменяя имя и обращение к точке, к которой осуществляется доступ. Проделываем то-же самое с переменной bezier1, заменив ее имя на secondHalf. Получаем в итоге: Тестируем, убеждаемся в том, что всё в порядке.
__________________
http://realaxy.com Последний раз редактировалось iNils; 20.12.2010 в 13:25. |
|
|||||
Регистрация: Apr 2001
Адрес: Moscow
Сообщений: 1,475
|
Рефакторинг intersect2Lines
Я напомню цель текущего шага: удаление случаев скрытого нетипизированного доступа.
Следующей мишенью наших действий станет метод Math2.intersect2Lines. Этот случай очень похож на предыдущий. Но, как я и обещал, чтобы разнообразить наш инструментарий способ рефакторинга будет выбран иной. Вырежем и вставим метод в класс LineSVG, сделаем его публичным. Сохраним оба документа (CTRL+SHIFT+S). Используя панель Problems перейдем на строку с ошибкой и добавим "LineSVG." перед вызовом метода. Поскольку мы намерены избавиться от скрытого нетипизированного доступа в аргументах метода, для начала снизим зависимость кода метода от аргументов. В самом начале метода объявим два объекта first и second типа LineSVG и создадим их используя аргументы метода. Везде далее в коде заменим использование аргументов на доступ через объекты LineSVG. Теперь аргументы используются только для создания объектов LineSVG, чего мы и добивались. Сделаем метод не статическим: удаляем static из объявления метода. Сохраняем документ, переходим на ошибку используя панель Problems. Создаем экземпляр LineSVG и от его имени вызываем метод intersect2Lines: var startArm : LineSVG = new LineSVG(point1, control1); var s:Point = startArm.intersect2Lines (point1, control1, control2, point2); Заменим аргументы на один: second : LineSVG и удалим в теле метода объявление second. Перейдем на ошибки в методе getQuadBez_RP. Скопируем строку объявления startArm, переименуем переменную в endArm и скопируем параметры из вызова метода intersect2Lines. После этого параметры заменим на endArm. В результате наших действий имя метода перестало отражать его суть. Переименуем в getLineIntersection и исправим вызов метода. В итоге, измененная чать метода Math2.getQuadBez_RP будет выглядеть так: var startArm : LineSVG = new LineSVG(point1, control1); var endArm : LineSVG = new LineSVG(control2, point2); var s:Point = startArm.getLineIntersection(endArm); Удалим объявления временных переменных ссылающихся на существующие значения.Подробно процесс на примере: - закомментируем строку 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 и получаем такой код: public function getLineIntersection(target : LineSVG) : Point { var dx1 : Number = end.x - start.x; var dx2 : Number = target.start.x - target.end.x; if (!(dx1 || dx2)) return null; var m1 : Number = (end.y - start.y) / dx1; var m2 : Number = (target.start.y - target.end.y) / dx2; if (!dx1) { return new Point(start.x, m2 * (start.x - target.end.x) + target.end.y); } else if (!dx2) { return new Point(target.end.x, m1 * (target.end.x - start.x) + start.y); } var xInt : Number = (-m2 * target.end.x + target.end.y + m1 * start.x - start.y) / (m1 - m2); var yInt : Number = m1 * (xInt - start.x) + start.y; return new Point(xInt, yInt); } Заменяем dx1 на currentDistanceX, а dx2 на targetDistanceX, исправляя подсвечивающиеся ошибки сразу после каждого переименования. Пробуем поставить const вместо var. Ошибок нет, так и оставим. Идем дальше вниз по коду. Добавляем фигурные скобки блоку if. Думайте что хотите, но я уверен, что if без фигурных скобок снижает читабельность кода, а пользы никакой. В вычислении переменных m1 и m2 видим, что используются вычисления, аналогичные currentDistanceX и targetDistanceX. Объявим аналогичные локальные константы currentDistanceY и targetDistanceY и присвоим им соответствующие значения скопировав из вычислений. Затем заменим вычисления на эти константы: const currentDistanceY : Number = end.y - start.y; const targetDistanceY : Number = target.start.y - target.end.y; var m1 : Number = currentDistanceY / currentDistanceX; var m2 : Number = targetDistanceY / targetDistanceX; const currentTangent : Number = currentDistanceY / currentDistanceX; const targetTangent : Number = targetDistanceY / targetDistanceX; В итоге имеем вот такой метод: public function getLineIntersection(target : LineSVG) : Point { const currentDistanceX : Number = end.x - start.x; const targetDistanceX : Number = target.start.x - target.end.x; if (!(currentDistanceX || targetDistanceX)) { return null; } const currentDistanceY : Number = end.y - start.y; const targetDistanceY : Number = target.start.y - target.end.y; const currentTangent : Number = currentDistanceY / currentDistanceX; const targetTangent : Number = targetDistanceY / targetDistanceX; if (!currentDistanceX) { return new Point(start.x, targetTangent * (start.x - target.end.x) + target.end.y); } else if (!targetDistanceX) { return new Point(target.end.x, currentTangent * (target.end.x - start.x) + start.y); } const intersectionX : Number = (-targetTangent * target.end.x + target.end.y + currentTangent * start.x - start.y) / (currentTangent - targetTangent); const intersectionY : Number = currentTangent * (intersectionX - start.x) + start.y; return new Point(intersectionX, intersectionY); } Потому, что программировать не надо бояться, программировать радоваться надо. А если серьезно, то обратите внимание на то, что мы уже довольно давно работаем с кодом, но до сих пор так и не вникали в суть его логики. Более того, если вы обратили внимание, в процессе рефакторинга код понемногу сам нам рассказывает о себе.
__________________
http://realaxy.com Последний раз редактировалось iNils; 20.12.2010 в 13:25. |
|
|||||
Регистрация: Apr 2001
Адрес: Moscow
Сообщений: 1,475
|
рефакторим getQuadBez_RP
Вернемся в класс Math2.
Здесь мы видим сиротливо приютившийся метод getQuadBez_RP и мы понимаем, что из-за его завистливости к данным других объектов и вселенского одиночества в классе, он обречен на перемещение, а класс будет удален. Поскольку в первую очередь мы намерены избавить метод от скрытого нетипизированного доступа в аргументах, то видим, что заменяться они будут на объект CubicBezierSVG. Туда и будем перемещать. Правда, мы видим, что метод также использует класс LineSVG и, вполне возможно, что он будет завистлив к данным этого класса. Но это не проблема. Мы впоследствии сможем переместить, если потребуется. Итак, приступим. Перемещаем метод вместе с комментариями в класс CubicBezierSVG, сохраняем все документы и идем исправлять образовавшиеся ошибки в окне Problems. Не буду расписывать - вы и сами знаете что делать. Тестируем. Затем удаляем класс Math2, при этом мысленно благодарим его за труд, который он делал в течение своей короткой, но очень яркой и полезной жизни. Возвращаемся к методу getQuadBez_RP. Как и в предыдущем случае, нам вначале потребуется избавиться от зависимости кода от аргументов метода, которые мы будем заменять на объект CubicBezierSVG. Для этого вначале метода создаем экземпляр класса CubicBezierSVG: И везде в коде метода вместо обращения к аргументам используем доступ через объект source. Чтобы гарантировать себя от невнимательности, действуем по простой схеме: - переименовываем аргумент; - новое имя используем только в инициализации объекта source; - остальные обращения заменяем на доступ через объект source. Вот что в итоге получилось: public static function getQuadBez_RP(start:Point, startControl:Point, endControl:Point, end:Point, k:Number, qcurves:Array):void { const source:CubicBezierSVG = new CubicBezierSVG(start, startControl, endControl, end); // find intersection between bezier arms var startArm : LineSVG = new LineSVG(source.start, source.startControl); var endArm : LineSVG = new LineSVG(source.endControl, source.end); var s:Point = startArm.getLineIntersection(endArm); // find distance between the midpoints var dx:Number = (source.start.x + source.end.x + s.x * 4 - (source.startControl.x + source.endControl.x) * 3) * .125; var dy:Number = (source.start.y + source.end.y + s.y * 4 - (source.startControl.y + source.endControl.y) * 3) * .125; // split curve if the quadratic isn't close enough if (dx*dx + dy*dy > k) { var sourceBezier : CubicBezierSVG = new CubicBezierSVG(source.start, source.startControl, source.endControl, source.end); var halves:Array = sourceBezier.split(); var firstHalf:CubicBezierSVG = halves[0] as CubicBezierSVG; var secondHalf:CubicBezierSVG = halves[1] as CubicBezierSVG; // recursive call to subdivide curve getQuadBez_RP (source.start, firstHalf.startControl, firstHalf.endControl, firstHalf.end, k, qcurves); getQuadBez_RP(secondHalf.start, secondHalf.startControl, secondHalf.endControl, source.end, k, qcurves); } else { // end recursion by saving points qcurves.push({p1x:source.start.x, p1y:source.start.y, cx:s.x, cy:s.y, p2x:source.end.x, p2y:source.end.y}); } } Удаляем ключевое слово static, сохраняемся и идем исправлять ошибки. Принцип, как всегда прост: - вначале метода объявляем переменную: - в коде, перед ошибочной строкой создаем экземпляр класса CubicBezierSVG: - копируем первые четыре аргумента метода getQuadBez_RP в аргументы конструктора CubicBezierSVG. - заменяем обращение к статическому методу на обращение через экземпляр source. - переходим к следующей ошибке, пока они не иссякнут. - тестируем проект. - не забываем радоваться тому, как у нас ловко всё получается.
__________________
http://realaxy.com Последний раз редактировалось iNils; 20.12.2010 в 13:26. |
|
|||||
Регистрация: Apr 2001
Адрес: Moscow
Сообщений: 1,475
|
А теперь можно заняться удалением теперь уже ненужных аргументов.
Заменим создание объекта source на присвоение ему this: И тут же тестируем и видим, что происходит переполнение стека. Причина совсем рядом: рекурсивный вызов метода. До тех пор, пока использовались аргументы, в методе создавались новые объекты CubicBezierSVG. А как только перешли на использование this наша невнимательность выползла наружу: мы не добавили объекты, от имени которых рекурсивно вызывается метод getQuadBez_RP. Исправим. Для начала откатимся назад до рабочего состояния: Вот тут-то и проявляется прелесть исправления кода короткими шагами. Протеституем, убедимся, что всё работает. Вот участок кода, который требует вмешательства: getQuadBez_RP (source.start, firstHalf.startControl, firstHalf.endControl, firstHalf.end, k, qcurves); getQuadBez_RP(secondHalf.start, secondHalf.startControl, secondHalf.endControl, source.end, k, qcurves); В этот момент мы должны остановиться и сказать себе: это не рефакторинг. Это действия по изменению логики приложения. К таким действиям мы должны подходить с совсем другими правилами.
__________________
http://realaxy.com Последний раз редактировалось iNils; 20.12.2010 в 13:27. |
Часовой пояс GMT +4, время: 15:55. |
|
« Предыдущая тема | Следующая тема » |
|
|