勿濫用 with...do

Object Pascal 的 with...do 語法的確很好用,可以少打好多字,正所謂水能載舟,亦能覆舟,好用的東西通常有些特殊事項要遵守,才能用得恰到好處。看看以下這個極端的例子:

procedure TForm1.Button1Click(Sender: TObject);
begin
  with Label1, Button1, ListBox1, ListBox2, Memo1 do
  begin
    Tag := 2;
    Caption := 'Who';
    Color := clRed;
    Items[0] := 'aa';
  end;
end;

先不管在語意上的混淆不清,如果你不完全了解 with...do 的行為,你能單單從程式碼預測其執行的結果嗎?
當然很少人寫程式會這麼極端,但是只要 with 裡面有兩個物件就夠你受的了,我的意思是,當你寫完程式三個月後再回來維護這些程式時,還要再花腦筋回憶當初撰寫的邏輯,並且試著找出到底是哪一行出了問題,你也許會像我一樣曾經納悶,明明屬性有設定了,怎麼都沒作用....如果是別人接手維護你的程式其痛苦程度可想而知了。此外,巢狀的 with...do 也要避免,原因也是一樣---程式出錯時很難抓蟲。

再看看這個例子,你能看出 TForm1.Button1Click 的執行結果嗎?

type
  TEmployee = record
    EmpID: string;
    EmpName: string;
  end;

{... TForm1 的類別定義省略 }

var
  Form1: TForm1;
  EmpArray: array [0..9] of TEmployee;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
var
  i: integer;
begin
  for i := Low(EmpArray) to High(EmpArray) do
  begin
    EmpArray[i].EmpID := IntToStr(i);
    EmpArray[i].EmpName := 'Emp' + IntToStr(i);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
begin
  Memo1.Clear;
  i := 0;
  with EmpArray[i] do
  begin
    while i < 10 do
    begin
      Memo1.Lines.Add(EmpName);
      Inc(i);
    end;
  end;
end;

end.

原本撰寫 Button1Click 的程式碼的人可能是想要把 EmpArray 裡面每個員工的姓名輸出到一個 Memo 元件,可是程式執行的結果是所有輸出的員工姓名都一樣,都是陣列的第 0 個元素的員工姓名。我曾經除錯一個別人寫的程式,就是這麼樣寫法,讓人弄不清楚他寫這樣的原意到底是要固定取第 0 個元素,還是要用迴圈取出所有陣列元素,卻因為不注意而寫成了這樣?你可以看得出來,這樣子使用 with...do 所造成的語意混淆不清有多麼危險。

那什麼時候該用,該怎麼用才算恰到好處,Ok,這有點兒主觀,我使用 with 的原則是:盡量少用,有用的話也是只對一個物件,絕對不要同時針對兩個物件,也不要使用巢狀 with 。在大部分的情況下,你都可以透過將物件指定給另一個指標,再利用該指標來操作的方式來取代 with 。像這樣:

var
  t: TTable;
  p: ^TEmployee;
begin
  p := EmpArray[0];
  p^.EmpNAme := 'Michael';
....
  t := EmployeeDataModule.EmployeeTable;
  t.Open;
....
end;

只要多打一些些字,就可能減少不必要的臭蟲及日後維護的成本,實在是很划算的交易,不是嗎?

蔡煥麟 Aug-30-2000

1