По мотивам:
Коротко. Про IStorage
Коротко. "Почему нужны тесты"
Я уже привык к тому, что выступаю в роли "капитана очевидность", но всё же - не могу не написать.
В Delphi - всё неплохо с контролем типов, но если только эти типы не атомарные.
А вот с атомарными типами - есть "шероховатости".
(Оговорюсь сразу - пишу не про "коня в вакууме", а "с колёс", про реальные проблемы, выявленные в процессе отладки и рефакторинга)
Попробую пояснить - что я имею в виду.
Пусть у нас есть объект-менеджер, который умеет распределять два типа ресурсов (в пределе - N).
Например этот объект выделяет блоки (двух разных типов) на файловой системе (потому и Int64).
И пусть он выглядит так:
-- в чём тут потенциальная проблема?
А в том, что можно написать так:
-- Т.е. распределили ресурс одного типа, а освобождаем - ресурс другого типа.
И компилятор тут нам - "не помощник".
И про проблемы мы можем узнать лишь по "косвенным признакам". Типа AV в Run-time или в лучшем случае - Assert.
Что можно сделать?
Можно попытаться написать так:
-- но и тут - компилятор - нам не поможет.
Можно даже попытаться написать так:
-- но и тут - компилятор - нам не поможет.
Что делать?
"Мой ответ" - избавится от "хакерства" и перейти от атомарных типов к неатомарным.
Как?
Ну банально например вот так:
- т.е. в данном случае - компилятор нам - помощник.
Почему записи, а не объекты?
Ну чтобы избежать "накладных расходов".
Хотя объекты - конечно - предпочтительнее.
Как только "бизнес-логика" становится "сложной" и начинает "упаковываться в экземпляры ресурсов".
Капитан-очевидность.
Но!
Ещё одна ремарка - пусть у нас есть даже не ресурсы, а просто "идентификаторы":
-- как их "случайно не перепутать"?
А всё так же:
Может быть кому-нибудь понравится.
Коротко. Про IStorage
Коротко. "Почему нужны тесты"
Я уже привык к тому, что выступаю в роли "капитана очевидность", но всё же - не могу не написать.
В Delphi - всё неплохо с контролем типов, но если только эти типы не атомарные.
А вот с атомарными типами - есть "шероховатости".
(Оговорюсь сразу - пишу не про "коня в вакууме", а "с колёс", про реальные проблемы, выявленные в процессе отладки и рефакторинга)
Попробую пояснить - что я имею в виду.
Пусть у нас есть объект-менеджер, который умеет распределять два типа ресурсов (в пределе - N).
Например этот объект выделяет блоки (двух разных типов) на файловой системе (потому и Int64).
И пусть он выглядит так:
type TmyResource = Int64; TmyAllocator = class public class function AllocResource1: TmyResource; class function AllocResource2: TmyResource; class procedure FreeResource1(var theResource: TmyResource); class procedure FreeResource2(var theResource: TmyResource); end;//TmyAllocator
-- в чём тут потенциальная проблема?
А в том, что можно написать так:
var l_Res : TmyResource; ... begin l_Res := TmyAllocator.AllocResource1; ... TmyAllocator.FreeResource2(l_Res); end;
-- Т.е. распределили ресурс одного типа, а освобождаем - ресурс другого типа.
И компилятор тут нам - "не помощник".
И про проблемы мы можем узнать лишь по "косвенным признакам". Типа AV в Run-time или в лучшем случае - Assert.
Что можно сделать?
Можно попытаться написать так:
type TmyResource1 = Int64; TmyResource2 = Int64; TmyAllocator = class public class function AllocResource1: TmyResource1; class function AllocResource2: TmyResource2; class procedure FreeResource1(var theResource: TmyResource1); class procedure FreeResource2(var theResource: TmyResource2); end;//TmyAllocator
var l_Res : TmyResource1; ... begin l_Res := TmyAllocator.AllocResource1; ... TmyAllocator.FreeResource2(l_Res); end;
-- но и тут - компилятор - нам не поможет.
Можно даже попытаться написать так:
type TmyResource1 = type Int64; TmyResource2 = type Int64; TmyAllocator = class public class function AllocResource1: TmyResource1; class function AllocResource2: TmyResource2; class procedure FreeResource1(var theResource: TmyResource1); class procedure FreeResource2(var theResource: TmyResource2); end;//TmyAllocator
var l_Res : TmyResource1; ... begin l_Res := TmyAllocator.AllocResource1; ... TmyAllocator.FreeResource2(l_Res); end;
-- но и тут - компилятор - нам не поможет.
Что делать?
"Мой ответ" - избавится от "хакерства" и перейти от атомарных типов к неатомарным.
Как?
Ну банально например вот так:
type TmyResource1 = record public rPosition : Int64; constructor Create(aPosition: Int64); end;//TmyResource1 TmyResource2 = record public rPosition : Int64; constructor Create(aPosition: Int64); end;//TmyResource2 TmyAllocator = class public class function AllocResource1: TmyResource1; class function AllocResource2: TmyResource2; class procedure FreeResource1(var theResource: TmyResource1); class procedure FreeResource2(var theResource: TmyResource2); end;//TmyAllocator ... constructor TmyResource1.Create(aPosition: Int64); begin rPosition := aPosition; end; constructor TmyResource2.Create(aPosition: Int64); begin rPosition := aPosition; end; ... class function TmyAllocator.AllocResource1: TmyResource1; begin Result := TmyResource1.Create(Self.AllocPos1); end; class function TmyAllocator.AllocResource2: TmyResource1; begin Result := TmyResource2.Create(Self.AllocPos2); end;
var l_Res : TmyResource1; ... begin l_Res := TmyAllocator.AllocResource1; ... TmyAllocator.FreeResource2(l_Res); // - Вот тут компилятор - ЗАРУГАЕТСЯ end;
- т.е. в данном случае - компилятор нам - помощник.
Почему записи, а не объекты?
Ну чтобы избежать "накладных расходов".
Хотя объекты - конечно - предпочтительнее.
Как только "бизнес-логика" становится "сложной" и начинает "упаковываться в экземпляры ресурсов".
Капитан-очевидность.
Но!
Ещё одна ремарка - пусть у нас есть даже не ресурсы, а просто "идентификаторы":
type TUserID = Integer; TGroupID = Integer;
-- как их "случайно не перепутать"?
А всё так же:
type TUserID = record public rID : Integer; constructor Create(anID: Integer); end;//TUserID TGroupID = record public rID : Integer; constructor Create(anID: Integer); end;//TGroupIDНу вот собственно и всё.
Может быть кому-нибудь понравится.
Дженерики никак не помогут разве?
ОтветитьУдалитьПопробуйте ответить на вопрос - "для чего они должны помочь".
ОтветитьУдалитьДля чего-то - точно могут :-)
угловые скобки стираются, гадство :(
ОтветитьУдалитьtype
ОтветитьУдалитьTmyResource1 = type Int64;
TmyResource2 = type Int64;
TmyAllocato{T} = class
public
class function AllocResource: T;
class procedure FreeResource(var theResource: T);
end;//TmyAllocator
var
l_Res: TmyResource1
...
l_Res := TmyAllocator{TmyResource1}.AllocResource;
...
TmyAllocator{TmyResource1}.FreeResource(l_Res); //тут уже не перепутает
TmyAllocator{TmyResource2}.FreeResource(l_Res); //тут ругнется компилятор
А вы пробовали? Это хотя бы компилировать... По-моему - нет.
Удалитьпонятно что это не рабочий код, я лишь сказал, что дженерики упрощают контроль типов (собственно заметка же об
ОтветитьУдалитьэтом), или Вам важен именно рабочий код? Можно придумать и рабочий... Но зачем?
Дженерики НЕ УПРОЩАЮТ контроль типов. Особенно атомарных. И если уж вы взялись "придумывать", то да - "придумывайте реальный код". И боюсь что про дженерики у вас странное представление. Могу конечно ошибаться.
ОтветитьУдалитьМысль была в чём?
УдалитьА вот тут:
type
TmyResource1 = type Int64;
TmyResource2 = type Int64;
-- дело в том, что TmyResource1 и TmyResource2 - для компилятора - НЕ РАЗЛИЧИМЫ.Что с дженериками, что БЕЗ НИХ. Собственно мысль была ТОЛЬКО В ЭТОМ.
Приведу более "тривиальный" пример - TCaption и TFileName. Они ведь:
type
TCaption = type String;
TFileName = type String;
Они ведь СЕМАНТИЧЕСКИ - РАЗНЫЕ, но СИНТАКСИЧЕСКИ - совместимы друг с другом.
Что ведёт к потенциальным ошибкам.
А вот:
type
TCaption = record
rValue : String;
end;//TCaption
TFileName = record
rValue : String;
end;//TFileName
-- что СЕМАНТИЧЕСКИ, что СИНТАКСИЧЕСКИ - НЕСОВМЕСТИМЫ. И это - "хорошо".
Тут - "компилятор помощник".
Так понятно? Или "я что-то придумываю"?