MyObject.OnEvent := procedure() begin MyObject.OnEvent := nil; MyField.MyProp := 'Value'; end;
Оказывается на MyObject.OnEvent := nil; счетчик ссылок у класса замыкания скрутится в 0 и замыкание отойдет в мир иной, а на следующей строчке привет AV.
MyObject.OnEvent := procedure() begin MyObject.OnEvent := nil; MyField.MyProp := 'Value'; end;
| 1. | На первом шаге у нас была одна постоянная ветка, бранчи использовались очень эпизодически и не было стабильной версии продукта. Да и так бывает. Бывало даже так, что одним коммитом фиксировалось сразу несколько задач. С этого мы начинали. | ||
| 2. | Потом появились ветки SVN (до SVN использовали VSS и Perforce). SVN - хорошая система. Какое-то время использовали собственную модель бранчевания, потом сдались и перешли на стандартную раскладку trunk, tags, branches. В branches мы создавали ветки только под большие задачи. Прошло много времени, но я хорошо помню боль и отчаянье от работы с ветками в SVN. Это было долго - долго создавать ветки, долго переключать, долго смотреть лог, а слияния веток превращались в испытание. Но с проблемами можно было мириться, если работать только с долго живущими ветками. | ||
| 3. | А следом появилось ревью кода. Это была практика, которая все перевернула. Сейчас мне сложно представить разработку без ревью. | ||
| 4. | Идеальным дополнением к ревью кода стал Git. Модель Git (DVCS) как нельзя лучше подходит для ревью кода. Следующие пункты раскроют это утверждение. | ||
| 5. |
Первым делом мы ввели правила написания текста коммита. Как писать коммит у нас жестко формализовано. Сами правила начали появляться еще при SVN, но только переход на Git заставил к ним относиться строго. Главное требование гласит: текст коммита обязан содержать мотивацию принятия решений и изменений, присутствующих в коде коммита. Перед выкладыванием на сервер каждая задача проходит ревью кода. Ревью кода выполняется с помощью инструмента CodeReviewer. И вот тут основной момент - ревьюверу задачи должно быть понятно все только на основе информации из коммита: непосредственно кода и текста описания коммита. Если возникают вопросы, то описание коммита перерабатывается. Можно найти множество плюсов подобного подхода:
Теперь текст коммита стал вот таким:
"fix: исправить фокусировку на следующию ячейку после нажатия Enter #issue 40436
При этом мы придерживаемся правила - код должен документировать сам себя. Комментарий в коде - признак плохого кода. Цель описания в коммите - не описание изменений, а описание почему именно так, а не иначе. В тексте коммита можно описать альтернативные варианты решения с плюсами и минусами. К слову сказать, текст коммита у нас так же ревьювируется как и код. В SVN это правило было тяжело соблюдать. Как правило, задача уходила на сервер одним коммитом, что бы не получить в централизованном репозитории не консистентного состояния. Описание всех изменений в одном коммите теряет свою красоту и эффективность. В git'е каждую задачу можно декомпозировать до небольших логических шагов, о чем в следующем правиле. | ||
| 6. |
Правила оформления задачи в ветках.
Каждую задачу, как правило, можно декомпозировать на несколько шагов. Отделив, например, рефакторинг от непосредственно кода, исправляющего ошибку. Каждый шаг - отдельный коммит в отдельной ветке задачи. В последствии вся ветка попадет в центральный репозиторий, сохраняя для поколений структурированное решение задачи. Теперь даже коммит, который был выполнен "заодно" и не являющийся непосредственно необходимым для закрытия задачи, находится в контексте задачи (в той же ветке), позволяя видеть цель и мотивацию автора.
Задача в SVN - это большой, размазанный по разным модулям патч. Больших усилий стоит разобраться в таком патче. Гораздо проще, когда каждая задача - это структурное решение, и можно пробежаться по отдельным шагам. В таком решении отделяются "зерна от плевел".
Репозиторий стал неотъемлемой частью кода проекта.
Пример: По ходу ревью в код могут вносится изменения, каждое такое изменение может вносится squash коммитом. Это коммит, который в последствие с помощью команды rebase можно объединить с другим коммитом (что бы ощутить мощь Git достаточно пробежаться по оглавлению способов изменить историю https://git-scm.com/book/ru/ и http://97things.oreilly.com/wiki/index.php/Record_your_rationale). Таким образом, можно будет и в ревью видеть развитие мысли автора. А после завершения ревью одной командой rebase можно изменить историю и получить красивое дерево коммитов, без лишнего мусора, объединив squash, fixup коммиты с базовыми. | ||
| 7. | Правило "юнит тесты до функциональности". Это правило говорит о том, что юнит тесты должны располагаться в дереве коммитов до исправления ошибки, которую они тестируют. В результате всегда можно проверить работу юнит теста до исправления ошибки и после. Убедиться, что тест и исправление написаны верно. Пример: |
"В первые годы работы над ракетной техникой практически никто из руководителей, критикующих завод, не мог конкретно сформулировать, что нужно сделать для повышения культуры производства, определить роль каждого начальника цеха, мастера и рабочего. Было слишком много общих решений. Устинов беспощадно расправлялся с начальниками цехов и производств за грязь и бескультурье. При посещениях завода он начинал с туалетов. Обычно в цехах задолго до подхода к туалету разносился характерный «аромат». В самих туалетах надо было ходить по лужам. Устинов приходил в ярость и гремел: «Какой сортир, такой и начальник цеха. Пока не добьетесь образцовой чистоты в своих сортирах, не будет чистоты и в цехах».С тех пор прошло очень много лет. Проблема чистоты общественных туалетов на наших заводах и в институтах так же, впрочем, как и в стране в целом, не решена. Это оказалось куда труднее, чем создать самое грозное ракетно-ядерное оружие и завоевать мировой приоритет в космонавтике.Явный дефицит культуры, общей производственной чистоты и гигиены до сих пор является одной из причин низкого качества многих отечественных изделий. За время войны и в последующие годы забота об элементарном комфорте в цехах, создание рабочему достойной и привлекательной общей обстановки считались излишней и непозволительной роскошью. Затраты на чистоту, комфорт, элементарный сервис с лихвой окупаются повышением производительности и качества. "
| Сообщения получаемые окном IE |
TMyWebBrowser = class(TWebBrowser) protected procedure WBProcessMessage; procedure InternalSetValue(const AValue: string); public procedure SetValue(const AValue: string); // Входная точка в примера procedure WaitWB; end; function EnumWindowsToFindIEHiddenProc(AHandle: HWND; AParam:NativeInt): boolean; stdcall; var IEHiddenHandle: Hwnd; implementation procedure TMyWebBrowser.SetValue(const AValue: string); begin InternalSetValue(AValue); // выполняем каким либо способом присваивание разметки WaitWB; // Ждем завершение загрузки документа. FooFunction; //Какой то функционал для работы которого необходим полностью загруженные html документ. end; procedure TMyWebBrowser.WaitWB; begin //Как то так обычно выглядит ожидание пока документ полностью не загрузиться while HTMLDocument2.readyState <> 'complete' do begin WBProcessMessage; // выбираем только нужные сообщения //Forms.Application.ProcessMessages; // выбираем все сообщения из очереди end; end; procedure TbtkHTMLEditor.WBProcessMessage; var msg: Windows.tagMSG; processID : THandle; begin IEHiddenHandle := 0; processID := GetCurrentProcessId; if EnumWindows(@EnumWindowsToFindIEHiddenProc, processID) then // �щем хендл окна IE в нашем процессе перебирая все окна if IEHiddenHandle <> 0 then // Проверяем найденный хендл валидный if PeekMessage(msg, IEHiddenHandle, 0, 0, PM_REMOVE) then // извлекаем из очереди оконных сообщений все сообщения для окна IEHiddenHandle begin Windows.DispatchMessage(msg); // Передаем извлеченные сообщения окну IE end; end; function EnumWindowsToFindIEHiddenProc(AHandle: HWND; AParam:NativeInt): boolean; var processId: NativeInt; classbuf: array[0..255] of Char; const IEWndClassName = 'Internet Explorer_Hidden'; begin result := true; if Windows.GetWindowThreadProcessId(AHandle,@processId) <> 0 then begin if AParam = processId then begin GetClassName(AHandle, classbuf, SizeOf(classbuf)); if lstrcmp(@classbuf[0], @IEWndClassName[1]) = 0 then begin IEHiddenHandle := AHandle; result := false; end; end; end; end;
procedure FmtStr(var Result: string; const Format: string; const Args: array of const; const FormatSettings: TFormatSettings); var Len, BufLen: Integer; Buffer: array[0..4095] of Char; begin .. end;
unit main; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public procedure DoOnMessage(var Msg: TMsg; var Handled: Boolean); end; function SetThreadStackGuarantee(var StackSizeInBytes: Cardinal): LongBool; stdcall; external 'kernel32.dll' name 'SetThreadStackGuarantee'; type proc = procedure(); TRaiseExceptionProc = procedure(ExceptionCode, ExceptionFlags: LongWord; NumberOfArguments: LongWord; Args: Pointer); stdcall; TExceptObjProc = function(P: PExceptionRecord): Pointer; var Form1: TForm1; oldRaiseExceptionProc: TExceptObjProc = nil; restoringAfterStackOverlow: boolean = false; stackGuaranteeSize: Cardinal; const PageSize = 4096; function _RaiseExeceptionProc(P: PExceptionRecord): Pointer{Exception}; implementation {$R *.dfm} {$POINTERMATH ON} function GetSegmentStackTop: Pointer; assembler; type NT_TIB32 = packed record ExceptionList: DWORD; StackBase: DWORD; StackLimit: DWORD; SubSystemTib: DWORD; case Integer of 0 : ( FiberData: DWORD; ArbitraryUserPointer: DWORD; Self: DWORD; ); 1 : ( Version: DWORD; ); end; asm MOV EAX, FS:[0].NT_TIB32.StackLimit end; function GetESP: Pointer; assembler; asm mov result, esp end; function GetMemoryInfo(addr: NativeUInt): TMemoryBasicInformation; inline; begin VirtualQuery(Pointer(addr), Result, SizeOf(Result)); end; procedure TerminateRecoveringAfterStackOverflow; begin Assert(restoringAfterStackOverlow); MessageBox(0, 'Не удалось восстановится после StackOverflow, приложение будет закрыто.', 'Все пропало', MB_ICONERROR); Halt(0); end; function RestoreStackPageGuard(APageAddress: Pointer): boolean; var oldProtectFlag: dword; begin result := VirtualProtect(APageAddress, PageSize, PAGE_READWRITE or PAGE_GUARD, oldProtectFlag); end; procedure StartRecoveringAfterStackOverflow; begin Assert(not restoringAfterStackOverlow); restoringAfterStackOverlow := true; RestoreStackPageGuard(GetSegmentStackTop); Application.OnMessage := Form1.DoOnMessage; end; procedure StopRecoveringAfterStackOverflow; begin Assert(restoringAfterStackOverlow); restoringAfterStackOverlow := false; Application.OnMessage := nil; // Так конечно лучше не делать, а использовать для этих // целей вместо обработчика OnMessage хук на сообщения end; procedure ValidateStackPageGuards; var memoryInfo: TMemoryBasicInformation; address, stackLimit: NativeUInt; guarded: Boolean; begin address := NativeUInt(GetESP); guarded := False; address := NativeUInt(GetMemoryInfo(address).BaseAddress); stackLimit := NativeUInt(GetSegmentStackTop); while not(guarded) and (address >= stackLimit) do begin memoryInfo := GetMemoryInfo(address); guarded := (memoryInfo.Protect and PAGE_GUARD) = PAGE_GUARD; address := address - PageSize; end; if not guarded then begin TerminateRecoveringAfterStackOverflow; end; end; function _RaiseExeceptionProc(P: PExceptionRecord): Pointer{Exception}; begin if (GetCurrentThreadId = MainThreadID) then begin if not restoringAfterStackOverlow then begin if Assigned(P) and (P.ExceptionCode = STATUS_STACK_OVERFLOW) then begin StartRecoveringAfterStackOverflow(); end end else ValidateStackPageGuards; end; if Assigned(oldRaiseExceptionProc) then result := oldRaiseExceptionProc(P); end; procedure WeNeedToGoDeeper(var i : integer); var a:array[0..4000] of char; begin inc(i); WeNeedToGoDeeper(i); end; procedure TForm1.Button1Click(Sender: TObject); var i: integer; begin i := 0; try WeNeedToGoDeeper(i); finally MessageBox(0, PChar(Format('На %d вложение',[i])), 'Ошибка StackOverflow', mb_ok); end; end; procedure TForm1.DoOnMessage(var Msg: TMsg; var Handled: Boolean); var i: integer; begin Assert(restoringAfterStackOverlow); ValidateStackPageGuards; for i := 1 to 16 do begin if not RestoreStackPageGuard(Pointer(NativeInt(GetSegmentStackTop) + PageSize * i)) then begin TerminateRecoveringAfterStackOverflow(); end; end; StopRecoveringAfterStackOverflow(); end; initialization stackGuaranteeSize := PageSize * 16; SetThreadStackGuarantee(stackGuaranteeSize); oldRaiseExceptionProc := ExceptObjProc; ExceptObjProc := @_RaiseExeceptionProc; end.