6 maneiras de fazer a mesma coisa, o que é considerado boas práticas?

As vezes tem tantas maneiras diferentes de fazer o mesmo código que nós ficamos na dúvida quanto a qual maneira usar. O que seria considerado "boa prática" pela comunidade e o que sua equipe entenderia melhor. Suponhamos que você esteja trabalhando dentro de um método de um Domain Service chamado UmDomainServiceChique(objetoDoDominio) que será chamado por uma API. Você tem uma regra de negócio chique para ser verificada que por enquanto chamarei de VerificaMinhaRegraChiqueComplexa(). Você chama UmDomainServiceChique(objetoDoDominio) e caso VerificaMinhaRegraChiqueComplexa() retorne true você vai querer que UmDomainServiceChique faça o que tem que fazer e a api retornar Ok 200, caso contrário você quer que a API responda um erro qualquer, tipo BadRequest, e retornar uma mensagem dizendo que VerificaMinhaRegraChiqueComplexa deu ruim. Eu vejo 6 maneiras de fazer isso, gostaria de saber a opinião de outrs devs sobre qual seria a maneira menos gambiarr

Interfaces no Delphi e destruição prematura de objetos / access violation


Recentemente me perguntaram no site da DevMedia se é correto ou não misturar interfaces e objetos em uma conversão temporária, como esta abaixo:

var C: TCarro;
begin

  C := TCarro.Create; //tenho um objeto, mas C não conta referencias
  C.Placa := 'ABC1234';
  (C as ICarro).Rodar; //converte e roda na mesma linha, mas zera o contador de referencias na proxima linha
  C.Parar;  //isso dá erro
Segue links da Thread:
http://www.devmedia.com.br/poo-dominando-o-uso-de-interfaces-revista-clube-delphi-134/22569
http://www.vitorrubio.com.br/downloads/TesteInterfaces.zip
http://www.vitorrubio.com.br/downloads/CD134_Interfaces_V2_Reformulado.zip

Isso por causa do problema de se zerar o contador de referências em interfaces tendo mais uma referência ao objeto em uma variável do tipo objeto e destruir esse objeto prematuramente.

Basicamente nesses casos o comportamento esperado seria um erro.
Pode acontecer de o objeto continuar acessível na memória, ou pelo menos alguns métodos que não usem dados internos do objeto, mas esse comportamento não é o esperado e é causado simplesmente por "sujeira" na memória.
Fiz um teste aqui e também funcionou, mesmo assim eu não recomendo. Diferenças na versão do Delphi ou na plataforma podem ocasionar erros.
Na época em que escrevi esse artigo eu usava Delphi XE em um computador de 64 bits, mas não me lembro a versão do Windows.
Hoje fiz o teste tanto no Delphi XE como no Delphi XE 4 usando Windows 7, mas num computador de 32 bits.

O porquê de eu esperar que essa construção cause um erro eu explico:
Dado que interfaces contam referências, então são "Reference Counted" e dado que você cria, converte e usa um objeto dentro de um mesmo método (como o evento click de um botão).

Se você criar o seu objeto já atribuindo ele à uma variável do tipo interface, assim:
var i: ICarro;
begin
 i:= TCarro.Create; //1 referencia a um TCarro
 (i as ICarro).DeslocarSe; //2 referencias ao TCarro //não precisa disso, apenas mostro como funciona normalmente
  i.OutroMetodo;
end; //final do método, 0 referencias ao mesmo TCarro, então  o objeto "hospedado" em I é destruido com free.
É esperado que um objeto apresente erro nesse tipo de conversão principalmente quando ele possui outro objeto dentro. Considere um objeto que tenha um StringList como um de seus campos.
var i: TCarro;
begin
i:= TCarro.Create; //1 referencia a um TCarro, no entanto I é um objeto, portanto não conta referências

(i as ICarro).DeslocarSe; //1 referencias ao TCarro hospedado em I convertido para ICarro

 i.OutroMetodo; //antes de chegar aqui já temos 0 referências (pois não existe mais a referência temporária que estava sendo usada na conversão para ICarro), portanto Free é acionado e o objeto em I é destruído "por engano". Aqui deve ocorrer um erro ao se executar OutroMetodo, pois o próprio objeto está destruído .
end;


Veja esse exemplo que eu criei no Delphi Xe 4, links:
http://www.devmedia.com.br/poo-dominando-o-uso-de-interfaces-revista-clube-delphi-134/22569
http://www.vitorrubio.com.br/downloads/TesteInterfaces.zip
http://www.vitorrubio.com.br/downloads/CD134_Interfaces_V2_Reformulado.zip

unit Unit1;

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;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;



  IVeiculo = interface  (iinterface)
  ['{1D2F0F1D-60CC-47BE-AD22-4DA59D87C7CA}']
    procedure DeslocarSe;
    procedure Parar;
  end;

  IVeiculoTerrestre = interface (IVeiculo)
  ['{CD718BB9-FE3A-4C19-8CE2-01EBB1843B96}']
    procedure DeslocarSe;
    procedure Parar;
    procedure Rodar;
  end;

  ICarro = interface (IVeiculoTerrestre)
  ['{3B167780-8EA0-4F59-9DBF-A9F68F54F595}']
    procedure DeslocarSe;
    procedure Parar;
    procedure Rodar;
  end;

  //TCarro = class(TInterfacedObject,  ICarro)  //nessa forma você pode atribuir a criação de um TCarro a uma variável ICarro, IVeiculoTerrestre e IVeiculo, mas não pode fazer conversões do tipo AS entre eles
  TCarro = class(TInterfacedObject,  ICarro, IVeiculoTerrestre, IVeiculo) //esta forma permite qualquer tipo de atribuição e conversão.
  private
    FPlaca: TStringList;
    function GetPlaca: string;
    procedure SetPlaca(value: string);
  public
    constructor Create;
    destructor Destroy; override;

    procedure DeslocarSe;
    procedure Parar;
    procedure Rodar;
    property Placa: string read GetPlaca write SetPlaca;
  end;


var
  Form1: TForm1;


implementation

{$R *.dfm}

{ TCarro }

constructor TCarro.Create;
begin
  FPlaca := TStringList.Create;
end;

procedure TCarro.DeslocarSe;
begin
  ShowMessage(Placa + ' Deslocando-se. ' + self.RefCount.ToString() + ' referências');
end;

destructor TCarro.Destroy;
begin
  FPlaca.Free;
  inherited;
end;

function TCarro.GetPlaca: string;
begin
  Result := FPlaca.Text;
end;

procedure TCarro.Parar;
begin
  ShowMessage(Placa + ' Parando.' + self.RefCount.ToString() + ' referências');
end;

procedure TCarro.Rodar;
begin
  DeslocarSe;
end;

procedure TCarro.SetPlaca(value: string);
begin
  FPlaca.Text := value;
end;

procedure TForm1.Button1Click(Sender: TObject);
var C: TCarro;
begin

  C := TCarro.Create; //tenho um objeto, mas C não conta referencias
  C.Placa := 'ABC1234';
  (C as ICarro).Rodar; //converte e roda na mesma linha, mas zera o contador de referencias na proxima linha
  C.Parar;  //isso dá erro
  C.Rodar;
  C.Parar;

end;

procedure TForm1.Button2Click(Sender: TObject);
var C: TCarro;
  I: IVeiculoTerrestre;
begin

  C := TCarro.Create; //tenho um objeto, mas C não conta referencias
  C.Placa := 'ABC1234';
  I := (C as ICarro); //Conversão joga  para I o controle da contagem de referencias
  I.Rodar; //uso normal
  I.Parar; //uso normal
  I.Rodar; //uso normal
  I.Parar; //uso normal

  C.Rodar; //Funciona normal, mas não se deve misturar objetos comuns com interfaces, por isso para de usar C uma vez convertido para I
  C.Parar; //Funciona normal, mas não se deve misturar objetos comuns com interfaces, por isso para de usar C uma vez convertido para I

end;

procedure TForm1.Button3Click(Sender: TObject);
var C: TCarro;
  I: IVeiculo;
begin

  C := TCarro.Create; //tenho um objeto, mas C não conta referencias
  C.Placa := 'ABC1234';
  I := (C as ICarro); //Conversão joga  para I o controle da contagem de referencias

  I.DeslocarSe;
  I.Parar;

  //I agora pode ser convertido para qualquer uma das interfaces ancestrais de ICarro sem perder o controle de referência
  (I as ICarro).Rodar;
  (I as ICarro).Parar;
  (I as IVeiculoTerrestre).Rodar;
  (I as IVeiculoTerrestre).Parar;
  (I as IVeiculo).DeslocarSe;
  (I as IVeiculo).Parar;

  C.Rodar; //Funciona normal, mas não se deve misturar objetos comuns com interfaces, por isso para de usar C uma vez convertido para I
  C.Parar; //Funciona normal, mas não se deve misturar objetos comuns com interfaces, por isso para de usar C uma vez convertido para I

end;

procedure TForm1.Button4Click(Sender: TObject);
var C: TCarro;
  iv1, iv2: IVeiculo;
  ivt1, ivt2: IVeiculoTerrestre;
  ic: ICarro;
begin

  C := TCarro.Create;
  C.Placa := 'ABC1234';
  ic  := (C as ICarro);

  ivt1 := (C as IVeiculoTerrestre);
  iv1  :=  (C as IVeiculo);

  ivt2 := (ic as IVeiculoTerrestre);
  iv2  :=  (ic as IVeiculo);

  ic.DeslocarSe;
  ic.Parar;
  ivt1.Rodar;
  ivt1.Parar;
  iv1.DeslocarSe;
  iv1.Parar;

  ivt2.Rodar;
  ivt2.Parar;
  iv2.DeslocarSe;
  iv2.Parar;

end;

procedure TForm1.Button5Click(Sender: TObject);
var C: TCarro;
  iv1, iv2: IVeiculo;
  ivt1, ivt2: IVeiculoTerrestre;
  ic: ICarro;
begin

  C := TCarro.Create;
  C.Placa := 'ABC1234';
  ic  := (C as ICarro);

  ic.DeslocarSe;
  ic.Parar;

  if Supports(c, IVeiculoTerrestre, ivt1) then
  begin
    ivt1.Rodar;
    ivt1.Parar;
  end;

  if Supports(c, IVeiculo, iv1) then
  begin
    iv1.DeslocarSe;
    iv1.Parar;
  end;

  if Supports(ic, IVeiculoTerrestre, ivt2) then
  begin
    ivt2.Rodar;
    ivt2.Parar;
  end;

  if Supports(ic, IVeiculo, iv2) then
  begin
    iv2.DeslocarSe;
    iv2.Parar;
  end;

end;

procedure TForm1.Button6Click(Sender: TObject);
var C: TCarro;
  iv1, iv2: IVeiculo;
  ivt1, ivt2: IVeiculoTerrestre;
  ic: ICarro;
begin

  C := TCarro.Create;
  C.Placa := 'ABC1234';
  ic  := ICarro(C);

  ivt1 := IVeiculoTerrestre(C);
  iv1  := IVeiculo(C);

  ivt2 :=  IVeiculoTerrestre(ic);
  iv2  :=  IVeiculo(ic);

  ic.DeslocarSe;
  ic.Parar;
  ivt1.Rodar;
  ivt1.Parar;
  iv1.DeslocarSe;
  iv1.Parar;

  ivt2.Rodar;
  ivt2.Parar;
  iv2.DeslocarSe;
  iv2.Parar;
end;

end.

Ele não deu erro como eu esperava no primeiro botão, mesmo assim eu não aconselho a fazer um uso de interfaces + objetos como aquele, principalmente quando seus objetos são passados para métodos ou retornados deles.
Infelizmente não consegui reproduzir esse erro hoje. Compilei e rodei esse código no Delphi XE, no Delphi XE4 e no Lazarus, e em todos os casos funcionou.
Mesmo assim, cuidado redobrado ao se passar variáveis do tipo interface como parâmetros.

Comentários

Postagens mais visitadas deste blog

Busca de CEP com o Lazarus - Parte 1 - UrlEncode

Botão Add This para adicionar seu post em qualquer rede

Uso de memória no SQL Server