type TPal = array [0..511] of TColor; TSavePalRec = array [1..50] of record Color:TColor; X:Integer; end; //--------------------------------------------------------------------------------------------- var PalProp:record mT, mW, mH, mW05:integer; end; ClkX, ClkY:integer; bMovePan, bDouble:boolean; SavePalRec:TSavePalRec; pan:array [1..100] of record x:integer; col:TColor; end; CountPan:integer; bDown:boolean; bIncDec:boolean; RedPos, GreenPos, BluePos:integer; RedCount, GreenCount, BlueCount:integer; ColArrR, ColArrG, ColArrB:array [0..1024] of byte; bMoveR, bMoveG, bMoveB:boolean; bmp:TBitMap; //--------------------------------------------------------------------------------------------- const NumbColor=19; ColorArray:array [0..NumbColor - 1] of TColor=($FFFFFF, $00FFFF, $FF00FF, $FFFF00, $0000FF, $FF0000, $00FF00, $C0FFFF, $FFC0FF, $FFFFC0, $C0C0FF, $FFC0C0, $C0FFC0, $C000FF, $00C0FF, $00FFC0, $C0FF00, $FFC000, $FF00C0); //--------------------------------------------------------------------------------------------- function TForm2.CreateAndSortPanel(X:integer; bRepaint:boolean; cl:TColor):integer; var i, j, tle, le:integer; col, tcol:TColor; begin for i:=1 to CountPan-1 do // определяем после которого маркера будет создаваемый if (pan[i].x < X) and (X < pan[i+1].x) then break; if i = CountPan then Exit; // если после последнего то выходим Result:=i; if (pan[i+1].x - pan[i].x) < PalProp.mW+2 then Exit; // если маркер некуда втиснуть между двумя ближайшими то выходим if ((pan[i].x + PalProp.mW+1) > X) or ((pan[i+1].x - PalProp.mW-1) < X) then Exit; // очень близко ставить маркер возле соседнего нельзя le:=pan[i+1].x; col:=pan[i+1].Col; pan[i+1].x:=X; pan[i+1].Col:=cl; for j:=i+2 to CountPan do begin tle:=pan[j].x; tcol:=pan[j].Col; pan[j].x:=le; pan[j].Col:=col; le:=tle; col:=tcol; end; CreatePanel(pb.Width); CurrentPan:=i+1; pan[CountPan].Col:=col; if bRepaint then begin RepaintPan(CurrentPan); RepaintPalitra(i, i+2); end; end; //--------------------------------------------------------------------------------------------- procedure TForm2.RepaintPan(Num: Integer); var x1, x2, y1, y2:integer; begin if Num = 1 then x1:=0 else x1:=pan[Num-1].x + PalProp.mW05+1; if Num = CountPan then x2:=pb.Width else x2:=pan[Num+1].x - PalProp.mW05-1; y1:=PalProp.mT + 1; y2:=PalProp.mT + PalProp.mH + 1; bmp.Canvas.Pen.Color:=clBtnFace; bmp.Canvas.Brush.Color:=clBtnFace; bmp.Canvas.Rectangle(x1, y1, x2, y2); // if Num = CurrentPan // then bmp.Canvas.Pen.Color:=clRed // else bmp.Canvas.Pen.Color:=clBlack; bmp.Canvas.Pen.Color:=clBlack; bmp.Canvas.Brush.Color:=pan[Num].Col; bmp.Canvas.Rectangle(pan[Num].x - PalProp.mW05, y1, pan[Num].x + PalProp.mW05, y2); x2:=x2 - x1; y2:=PalProp.mH+2; BitBlt(pb.Canvas.Handle, x1, y1, x2, y2, bmp.Canvas.Handle, x1, y1, SRCCOPY); end; //--------------------------------------------------------------------------------------------- procedure TForm2.RepaintAllPan; var i:integer; begin bmp.Canvas.Pen.Color:=clBtnFace; bmp.Canvas.Brush.Color:=clBtnFace; bmp.Canvas.Rectangle(0, PalProp.mT + 1, pb.Width, PalProp.mT + PalProp.mW + 1); for i:=1 to CountPan do begin // if i = CurrentPan // then bmp.Canvas.Pen.Color:=clRed // else bmp.Canvas.Pen.Color:=clBlack; bmp.Canvas.Pen.Color:=clBlack; bmp.Canvas.Brush.Color:=pan[i].Col; bmp.Canvas.Rectangle(pan[i].x - PalProp.mW05, PalProp.mT + 1, pan[i].x + PalProp.mW05, PalProp.mT + PalProp.mW + 1); end; BitBlt(pb.Canvas.Handle, 0,PalProp.mT + 1, pb.Width, PalProp.mH, bmp.Canvas.Handle, 0, PalProp.mT + 1, SRCCOPY); end; //---------------------------------------------------------------------------------------------------------------------------------------------------------- procedure TForm2.RepaintPalitra(Num1, Num2:integer); // перерисовка var i, j, a, b:integer; begin if Num1 < 1 then Num1:=1; // проверка выхода за пределы (они есть в Panels_onDblClick) if Num2 > CountPan then Num2:=CountPan; b:=pan[Num1].x; for i:=Num1 to Num2-1 do begin a:=pan[i+1].x - pan[i].x; for j:=0 to a do begin bmp.Canvas.Pen.Color:=ColorChange(pan[i].Col, pan[i+1].Col, a, j); bmp.Canvas.MoveTo(b+j, 0); bmp.Canvas.LineTo(b+j, PalProp.mT); end; b:=b+a; end; BitBlt(pb.Canvas.Handle, pan[Num1].x, 0, pan[Num2].x - pan[Num1].x, PalProp.mT, bmp.Canvas.Handle, pan[Num1].x, 0, SRCCOPY); if not Form1.bFirstLoad then SaveChangeToMainForm; end; //--------------------------------------------------------------------------------------------- procedure TForm2.SaveChangeToMainForm; var i:integer; begin for i:=0 to 511 do Form1.pal[i]:=bmp.Canvas.Pixels[i, 1]; // заганяем новую палитру Form1.DrawFromArray(Form1.FractArr, Rect(0, 0, Form1.pb.Width - 1, Form1.pb.Height - 1), Form1.Bmp); Form1.pbPaint(self); end; //--------------------------------------------------------------------------------------------- procedure TForm2.RandomPalitra; var i, j, k:integer; r:real; begin if cb1.Checked then begin Randomize; CountPan:=2; pan[1].x:=0; pan[1].col:=RGB(Random(256), Random(256), Random(256)); pan[2].x:=pb.Width; pan[2].col:=pan[1].col; k:=Random(10) + 3; r:=pb.Width / (k + 1); CreateAndSortPanel(9, false); for i:=1 to k do begin j:=Round(i*r); if Random(2) = 1 then CreateAndSortPanel(j - PalProp.mW - 1, false); CreateAndSortPanel(j, false, ColorArray[Random(NumbColor - 1)]); if Random(2) = 1 then CreateAndSortPanel(j + PalProp.mW + 1, false); end; CreateAndSortPanel(pb.Width - PalProp.mW - 1, false); RepaintAllPan; RepaintPalitra(1, CountPan); end else begin if Form1.smnPerelyv.Checked then Form1.smnPerelyvClick(Self); RedCount:=Random(9)+1; GreenCount:=Random(9)+1; BlueCount:=Random(9)+1; RedPos:=Random(511)+1; GreenPos:=Random(511)+1; BluePos:=Random(511)+1; bMoveR:=Random(2)=1; bMoveG:=Random(2)=1; bMoveB:=Random(2)=1; ChangeParam; end; end; //--------------------------------------------------------------------------------------------- function ColorChange(col1, col2: TColor; R, i: real): TColor; var cr, cg, cb:real; cr1, cg1, cb1:byte; cr2, cg2, cb2:byte; dcr, dcg, dcb:byte; begin cr1:=GetRvalue(col1); cr2:=GetRvalue(col2); cg1:=GetGvalue(col1); cg2:=GetGvalue(col2); cb1:=GetBvalue(col1); cb2:=GetBvalue(col2); dcr:=abs(cr1 - cr2); dcg:=abs(cg1 - cg2); dcb:=abs(cb1 - cb2); if cr1 <> cr2 then begin if cr1 < cr2 then cr:=cr1 + dcr*i/R; if cr1 > cr2 then cr:=cr1 - dcr*i/R; end else cr:=cr1; if cg1 <> cg2 then begin if cg1 < cg2 then cg:=cg1 + dcg*i/R; if cg1 > cg2 then cg:=cg1 - dcg*i/R; end else cg:=cg1; if cb1 <> cb2 then begin if cb1 < cb2 then cb:=cb1 + dcb*i/R; if cb1 > cb2 then cb:=cb1 - dcb*i/R; end else cb:=cb1; Result:=RGB(Round(cr), Round(cg), Round(cb)); end;Выложил я только 1/10 часть кода, которая отвечает за формирование рендомной палитры, по которой потом отрисуется фарктал в красивых, пестрых красках. Вчера он мне понадобился. Я хотел сделать то же но на Java. Я хотел приделать эту же палитру к своему недавнему минипроектику "Рисуем фракталы на Java".
Что я хотел донести ребятам - так это те эмоции, которые я получал когда вчера портировал этот код на Java, а потом отлаживал его. На все про все 2 часа времени. Хотя это могло занять 10 минут, если бы код был поддерживаем. Я глядя в код, не мог сразу понять что он делает. Не мог вспомнить что я хранил в переменных с хитромудрным названием. Я знал лишь только, что он работает и работает так как надо. Так же я знал, что написать такой же с нуля у меня займет существенно больше времени, чем портирование существующего. И я взялся за рефакторинг.
Первым делом я написал некоторое подобие тестов и сделал механическое превращение (портирование) кода Pascal в Java. Работа не сложная, но требует внимания, ведь чем больше я ошибок тут сделаю, тем больше потом времени потрачу на отладку. 30 минут и код компилировался в Idea. Первая зеленая полоса! Я даже на радостях закоммитился, хотя понимал, что ничего оно не работает.
Потом был небольшой тестовый класс, который генерит палитру, рисует ее в bmp и сохраняет в BMP файл. А я уже чекаю результат. Конечно же он рисовал не то, что требовалось. Иногда случается, что код работает сразу, но я рад что это было не так. Пришлось его отлаживать.
Еще пол часа внимательного рефакторинга, и постепенного разтуманивания прдназначения методов и переменных. Потом полировка и тюнинг под новые нужны. Тоже пол часа. Итого я получил вот это
public class RandomPalette implements Palette { class Marker { int x; int color; public Marker(int x, int color) { this.x = x; this.color = color; } } private static final int MW = 8; // ширина маркера private static final int[] colorArray = new int[]{ 0xFFFFFF, 0x00FFFF, 0xFF00FF, 0xFFFF00, 0x0000FF, 0xFF0000, 0x00FF00, 0xC0FFFF, 0xFFC0FF, 0xFFFFC0, 0xC0C0FF, 0xFFC0C0, 0xC0FFC0, 0xC000FF, 0x00C0FF, 0x00FFC0, 0xC0FF00, 0xFFC000, 0xFF00C0}; private List<Marker> markers = new LinkedList<Marker>(); private int[] palette; public RandomPalette(int size) { palette = new int[size]; int color = getRandomColor(); markers.add(new Marker(0, color)); markers.add(new Marker(size, color)); int count = random(size / (MW * 3)) + 3; double r = size / (count + 1); addBlackMarker(MW + 1); for (int i = 1; i <= count; i++) { int j = (int) (i * r); if (yesOrNo()) { addBlackMarker(j - MW - 1); } addMarker(j, getRandomColor()); if (yesOrNo()) { addBlackMarker(j + MW + 1); } } addBlackMarker(size - MW - 1); calculatePalette(); } private int getRandomColor() { return colorArray[random(colorArray.length)]; } private boolean yesOrNo() { return random(2) == 1; } private void addBlackMarker(int x) { addMarker(x, 0); } private void calculatePalette() { int x = markers.get(0).x; for (int i = 0; i < markers.size() - 1; i++) { int length = markers.get(i + 1).x - markers.get(i).x; for (int dx = 0; dx < length; dx++) { palette[x + dx] = colorChange(markers.get(i).color, markers.get(i + 1).color, length, dx); } x = x + length; } palette[0] = 0; } private int colorChange(int from, int to, double len, double x) { double red = change(getR(from), getR(to), len, x); double green = change(getG(from), getG(to), len, x); double blue = change(getB(from), getB(to), len, x); return rgb((int) red, (int) green, (int) blue); } private double change(double from, double to, double len, double x) { if (from == to) { return from; } double delta = Math.abs(from - to) * x / len; if (from < to) { return from + delta; } else { return from - delta; } } private int getR(int col) { return (col & 0x0000FF); } private int getG(int col) { return (col & 0x00FF00) >>> 8; } private int getB(int col) { return (col & 0xFF0000) >>> 16; } private int rgb(int r, int g, int b) { return (r) | (g << 8) | (b << 16); } private void addMarker(int x, int color) { // определяем после которого маркера будет создаваемый int index; for (index = 0; index < markers.size(); index++) { if ((markers.get(index).x < x) && (x < markers.get(index + 1).x)) { break; } } // если после последнего то выходим if (index == markers.size()) { return; } // если маркер некуда втиснуть между двумя ближайшими то выходим if (markers.get(index + 1).x - markers.get(index).x < MW + 2) { return; } // очень близко ставить маркер возле соседнего нельзя if ((markers.get(index).x + MW + 1 > x) || (markers.get(index + 1).x - MW - 1 < x)) { return; } markers.add(index + 1, new Marker(x, color)); } private int random(int n) { return new Random().nextInt(n); } @Override public int getColor(int r) { return palette[r]; } @Override public int getSize() { return palette.length; } }И я более чем уверен, что глядя на этот код спустя некоторое время я буду считать его говнокодом. Если этого не случится – значит. Не даром, если заметил, я этот код назвал «это». «Это» только оно сейчас работает… Вообще считаю, стоит относиться к своему коду не как к произведению искусства, а как к продукту жизнедеятельности, тому что могло быть чуточку лучше, раз уж появилось на этот свет.
Что есть code review? Это всего лишь озвучивания той дельты, которая существует у двух специалистов в их опыте. Если мне больше нечего сказать напарнику по поводу его кода, это вовсе не значит что его код идеален. Я уже завтра могу прочитать новую статью и пережив ее понять, что код не такой уж и совершенный, как казалось вчера. Кроме того ревью может сделать более сеньорный специалист и рассказать о том, на какие грабли он наступал, на какие еще не наступали я с моим напарником. И то, что этому сеньорному специалисту в какой-то момент больше нечего будет сказать нам - значит лишь одно - мы написали такой код, который хотел бы написать он сам. А завтра все поменяется.
По этой причине код стоит пересматривать регулярно. Если в какой-то момент про код забыли - он устаревает со скоростью света. Но если в код вносились изменения сегодня - велика вероятность, что в него будут вноситься изменения завтра. Такой код после внесения серии изменений стоит пересматривать его коллективно.
Ну вот как бы и все, что хотел запечатлеть в памяти. Надеюсь кому-то пригодится...
Комментариев нет:
Отправить комментарий