Formatowanie kodu w Delphi

Adam Boduch

Najważniejsze, aby program działał zgodnie z naszymi zakładanymi oczekiwaniami. Ważne jest również to, aby działał szybko, był dobrze zoptymalizowany, ważne są użyte w nim algorytmy itp. Styl zapisu tego kodu często jest ignorowany, spychany na dalszy plan. Dla kompilatora nie jest istotne jak zapisany jest kod, czy zostały użyte wielkie czy małe znaki. Równie dobrze cały kod może być zapisany w jednej linii - byleby posiadał prawidłową składnię. Styl kodowania odgrywa znaczącą rolę w pracy zespołowej. Ba! Ustalenie jednolitego standardu pracy jest bardzo ważnym elementem pracy zespołowej, pozwalającym zwiększyć wydajność programisty. Szczególnie, ma to znaczenie w projektach Open Source, gdzie komunikacja pomiędzy programistami bywa utrudniona, a praca odbywa się "na odległość".

1 Wprowadzenie
2 Pliki źródłowe
3 Moduły
     3.1 Informacja o prawie autorskim
     3.2 Nazwa modułu
     3.3 Włączanie modułów
     3.4 Deklaracja klasy
4 Spacje i odstępy
     4.5 Puste linie
     4.6 Spacje
          4.6.1 Kiedy spacji być nie może
     4.7 Wcięcia
     4.8 Długość linii
5 Instrukcje
     5.9 Zmienne
     5.10 Typy danych
     5.11 Instrukcja warunkowa if
     5.12 Instrukcja warunkowa case
     5.13 Pętla for
     5.14 Pętla while
     5.15 Pętla repeat
     5.16 Instrukcja Try
6 Nazewnictwo
     6.17 Język
     6.18 Klasy/Interfejsy
     6.19 Pola klasy
     6.20 Nazewnictwo metod
     6.21 Pozostałe
     6.22 Notacja węgierska
7 Komentarze
     7.23 Komentarze grupowe
     7.24 Komentarze jednej linii

Podobnie jak człowiek, w okresie dorastania kształtuje sobie charakter pisma, tak też początkujący programista kształtuje styl kodowania. Warto ukształtować sobie ten "styl" na samym początku nauki, zaowocuje to tym, iż będziesz pisał czytelniejsze programy, nawet jeżeli pisane są one na użytek własny.

Wielu programistów pochłoniętych nauką samego języka, zapomina o stylu kodowania. Nie przejmuj się - nawyk tworzenia "brzydkiego" kodu można łatwo zmienić. Niniejszy artykuł przedstawia konwencje stosowaną przez programistów Borland, która jest polecana i stosowana przez większość programistów Delphi. Nawet jeżeli piszemy aplikacje jedynie dla siebie, które pewnie nie ujrzą prędko "światła dziennego", warto stosować styl zaprezentowany w tym tekście. Pozwoli to wyrobić Czytelnikowi nawyk pisania poprawnego kodu, który na pewno przyda się jeżeli pragniemy kontynuuować naukę Delphi i pracować w zawodzie związanym z programowaniem.

Styl kodowania programistów Borland można zobaczyć przeglądając kod źródłowy biblioteki VCL.

Wprowadzenie

Na pojęcie "styl kodowania" składa się wiele elementów, począwszy od ilości wcięć w kodzie, po położenie instrukcji Else w instrukcji warunkowej. Wszystkie te, na pozór - szczegółowe elementy, odgrywają istotną rolę; dobrze użyte sprawiają iż kod jest zapisany czytelnie.

Pliki źródłowe

Pliki źródłowe Delphi posiadają rozszerzenie .pas oraz .dpr. Plik główny .dpr jest podstawowym plikiem projektu Delphi; kody źródłowe poszczególnych modułów zapisane są w plikach .pas. Oczywiście cały projekt może składać się z innych plików (np. plików formualrza), ale nie to jest istotne. Istotne jest nazewnictwo tychże plików. Należy stosować styl wielbłądzi, tzn. pierwsza litera każdego słowa musi być duża - np.: MyFile.pas, TheGame.dpr itp. Rozszerzenie powinno być pisane małymi literami.

Polecamy stosować konwencję, w której moduł będący modułem formularza Delphi, będzie posiadał końcówkę frm - np. MainFrm.pas, AboutFrm.pas, OptionFrm.pas itd. Natomiast formularz znajdujący się w tym module będzie posiadał końcówkę Form - np. TMainForm, TAboutForm, TOptionForm itp.

Moduły

Typowy moduł Delphi powinien składać się z następujących elementów:

  1. Nagłówka Copyright
  2. Nazwy modułu (Unit)
  3. Sekcji Uses
  4. Sekcji Interface
  5. Sekcji Implementation
  6. Słowa kluczowego End zamykającego moduł.
    Aby zwiększyć przejrzystość, każdy element powinien być oddzielony przynajmniej jedną linią pustą.

Informacja o prawie autorskim

U góry każdego modułu czy pliku źródłowego projektu, powinna znaleźć się informacja o prawie autorskim i autorze. W najprostszym wydaniu może wyglądać ona tak:

(******************************************************)
(*                                                    *)
(*        My Cool Component v. 1.0                    *)
(*        Copyright (c) My Name 2006                  *)
(*                                                    *)
(******************************************************)

unit MainFrm;

Nazwa modułu

Każdy moduł powinien mieć nazwę, którą poprzedza słowo kluczowe Unit. Słowo kluczowe powinno być pisane małymi literami:

unit MainFrm;

Włączanie modułów

W sekcji Uses należy określić jakie moduły będą włączane do naszego modułu. Każdy moduł powinien być oddzielony od drugiego przecinkiem, po którym następuje spacja:

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs;

Lista modułów nie powinna wykraczać poza 80 znak w linii. Jeżeli lista nie mieści się w jednej linii, przenosimy moduł do drugiej linii (tak jak w tym wypadku moduł Dialogs). Zwróć jednak uwagę, iż w drugiej linii zastosowano wcięcie. Niedozwolony jest bowiem taki zapis:

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;

Deklaracja klasy

Deklaracja klasy rozpoczyna się od dwóch spacji, po której następuje jej nazwa. Nazwa klasy w języku Object Pascal (Delphi) zawsze rozpoczyna się od litery T. Nie jest to wymóg techniczny, a jedynie "tradycja". Nazwę klasy, od znaku równości oraz słowa class, oddzielają spacje:

type
  TMainForm = class(TForm)

Sekcje Private, Public, Protected, Published, Strict private oraz Strict protected powinny być poprzedzone dwoma spacjami:

type
  TMainForm = class(TForm)
  private
  public
  protected
  published
  end;

Spacje i odstępy

Odstępy, spacje, wcięcia. To wszystko ma znaczenie podczas pisania czytelnego kodu. Spójrz na poniższy przykład, który pokazuje z całą pewnością jak nie należy pisać:

procedure TMainForm.Button1Click(Sender: TObject);
var
i:integer;
lancuch:string;
begin
lancuch:='Ala ma kota';
for i:=0 to 10 do begin
memo1.lines.add('Łancuch nr: '+inttostr(i)+' '+lancuch);
end;
end;

Taki kod jest kompletnie nieczytelny i beznadziejny. Spójrz jak powinien wyglądać prawidłowo sformatowany kod:

procedure TMainForm.MyButtonClick(Sender: TObject);
var
  I : Integer;
  S : String;
begin
  S := 'Ala ma kota';
  for I := 0 to 10 do
  begin
    memFoo.Lines.Add('Łancuch nr: ' + IntToStr(I) + ' ' + S);
  end;
end;

Sam odpowiedz sobie na pytanie, który sposób zapisu jest według Ciebie lepszy.

Puste linie

Puste linie w znacznym stopniu mogą poprawić czytelność kodu. Stosuj puste linie intuicyjnie aczkolwiek uważaj aby nie przesadzić. Naturalnie pomiędzy zakończeniu deklaracji klasy słowem kluczowym end, a sekcją interface (przykładowo) powinna być linia przerwy.

Spacje

Kluczowym elementem są spacje. Zamiast pisać:

i:integer;

Czytelniej będzie, jeżeli pomiędzy znakiem dwukropka znajdzie się odstęp:

i : integer;

Ta sama zasada dotyczy wszelkich operatorów, pomiędzy którymi powinny znaleźć się spacje.

Kiedy spacji być nie może

W kilku sytuacjach użycie spacji jest nieodpowiednie:

  • pomiędzy nazwą metody/funkcji/procedury a nawiasem otwierającym listę parametrów,
  • po lub przed operatorem kropki (.),
  • przed zamknięciem lub po otwarciu nawiasu,
  • przed zamknięciem lub po otwarciu nawiasu kwadratowego,
  • po zastosowaniu operatora @,
  • przed zastosowaniem symbolu ^

Oto kilka przykładów błędnego zapisu stylu:

procedure Foo ( var S : String );
procedure Foo (var S : String);
MyFoo := @ MyBar;
MyFoo ^ := 10;
MyFoo[ 10 ] := 10;

Poprawne wersje:

procedure Foo(var S : String);
MyFoo := @MyBar;
MyFoo^ := 10;
MyFoo[10] := 10;

Wcięcia

W Delphi stosujemy zawsze podwójne wcięcia w stosunku do bloku. Przykład:

begin
  S := 'Ala ma kota';
  for I := 0 to 10 do
  begin
    memFoo.Lines.Add('Łancuch nr: ' + IntToStr(I) + ' ' + S);
  end;
end;

Jak widzisz, blok begin i end stanowią blok do którego powinniśmy się odnosić. Tak więc wcięcie instrkcji w tym bloku wynosi 2 spacje. Kolejny blok (begin..end) w pętli for oznacza kolejne wcięcie o 2 spacje. W dłuższych listingach o wiele łatwiej zorientować się do którego bloku należy dana instrukcja.

Długość linii

Linie kodu źródłowego nie powinny przekraczać 80 znaków. W edytorze kodu w Delphi, tę granicę określa pionowa linia (w domyślnych ustawieniach wskazuje na 80 znak).

Instrukcje

Instrukcje powinny być zapisywane jedna pod drugą, bez wcięć:

begin
  A := B;
  C := B;
  X := Y; B := A; // źle
end.

W przypadku, gdy mamy do czynienia z długą instrukcją można zapisać ją w kolejnej linii, z zastosowaniem wcięcia w wielkości 2 spacji:

Foo :=
  (VeryVeryLongStatement / OtherVerVeryLongStatement);

Zmienne

Zmienne lub stałe (globalne lub lokalne) powinny być zapisane w bloku var, z wcięciem w wielkości 2 znaków. Nazwy zmiennych powinny używać stylu wielbłądziego. Kilka przykładów złej deklaracji zmiennych:

var s:string;
w:word;
my_long_variable:byte;

Prawidłowa deklaracja:

var
  S : String;
  W : Word;
  MyLongVariable : Byte;

Nazwa stałej powinna zaczynać się od wielkiej litery, nawet gdy mamy do czynienia ze zmiennymi jednoznakowymi (najczęściej wykorzystywane w pętlach For):

var
  I, J, K : Integer; 

Pomiędzy nazwą zmiennej a typem powinna znaleźć się spacja. Możliwe jest grupowanie zmiennych jeżeli są tego samego typu, tak jak przedstawiono to w przykładzie powyżej.

Typy danych

Pierwszy znak typów takich jak Integer, String itp. powinien być pisany wielką literą:

var
  S : string; // źle
  I : Integer; // dobrze

Instrukcja warunkowa if

Kod umieszczony w instrukcji warunkowej powinien posiadać 2 spacje wcięcia:

if X < Y then DoFoo; // źle
if X < Y then
  DoFoo; // dobrze

Zalecane jest jedna grupowanie instrukcji nawet jeżeli w instrukcji warunkowej do wykonania mamy jedynie jedną instrukcję. Dodatkowo, słowo kluczowe begin powinno być zapisane w nowej linii:

if X < Y then begin
  DoFoo;
  DoBar;
end; // źle

if X < Y then
begin
  DoFoo;
  DoBar;
end;

To samo tyczy się instrukcji Else:

if X < Y then
begin
  DoFoo;
  DoBar;
end else begin
  DoFoo2;
end; // źle

if X < Y then
begin
  DoFoo;
  DoBar;
end else
begin
  DoFoo2; 
end; // dobrze

Instrukcja warunkowa case

Przykład prawidłowego zastosowania instrukcji Case:

case MyFoo of
  0: MyBar := 10;
  1: MyBar := 20;
end;

Pamiętajmy o wcięciu w wielkości 2 spacji. Jeżeli wymagane jest pogrupowanie instrukcji, prawidłowy wygląd kodu powinien wyglądać następująco:

case MyFoo of 
  0: 
    begin
    { instrukcje } 
    end;

  1:
    begin
    { instrukcje }
    end;
end;

Pętla for

Przykład złego zapisu pętli For:

for I := 0 to 10 do begin
{ kod... }
end;

Podobnie, jak w przypadku instrukcji warunkowej słowo kluczowe Begin powinno być zapisane w kolejnej linii:

for I := 0 to 10 do
begin
  { kod - 2 spacje wcięcia }
end;

Pętla while

Tutaj sprawa wygląda podobnie jak w przypadku pętli For:

// źle
while I < Count do begin
  X := Y;
  Inc(I);
end;

// dobrze
while I < Count do
begin
  X := Y;
  Inc(I);
end;

Pętla repeat

Prawidłowy zapis pętli Repeat powinien wyglądać następująco:

repeat
  Count := X;
  X := Y; 
until I < 25;

Instrukcja Try

Kod znajdujący się w bloku Try .. Except (lub Finally) powinien być zapisany z wcięciem w wielkości 2 spacji:

try
  try
    DoFoo;
  except
    raise;
  end;
finally
  Bar;
end;

Nazewnictwo

Oprócz słów kluczowych języka Delphi, które powinny być pisane wyłącznie małymi literami, w programie będziesz musiał deklarować własne zmienne, klasy czy typy danych. W takim wypadku należy stosować tzw. styl wielbłądzi. Nazwę każdej zmienne czy klasy, a także metody/prodedury/funkcji powinna rozpoczynać wielka litera. Każdy wyraz będący składową nazwy powinien rozpoczynać się wielką literą - np.:

procedure mybadprocedure; // źle
procedure MyCoolProcedure; // dobrze

Niedozwolone jest natomiast użycie znaków _ (konwencja znana programistom C/C++ czy PHP) w ramach rozdzielenia składowych nazwy - np.:

procedure my_cool_procedure; 

Wyjątkiem są metody, które mają obsługiwać komunikaty. W takim wypadku, nazwę komunikatu należy pisać wielkimi literami:

procedure wmLButtonDown(var Msg: TMessage); message WM_LBUTTONDOWN; 

Język

Programiści firmy Borland nie określili języka w jakim ma być pisany kod, gdyż dla nich jest to jasne. Zalecane jest jednak używanie języka międzynarodowego i uniwersalnego jakim jest angielski. Jeżeli pracujesz nad projektem Open Source, który w dodatku jest projektem międzynarodowym, takim wymóg jest chyba oczywisty; polecamy jednak stosowanie tego języka w całości kodu źródłowego. Osobom, które nie mówią dobrze w języku angielskim taka praktyka pomoże na lepsze poznanie tego języka.

Klasy/Interfejsy

Nazwa klas powinna zaczynać się od litery T, natomiast interfejsu - od litery I. Nie jest to wymóg techniczny, ale przyjęta tradycja. Również deklarując nowy typ (niekoniecznie klasowy), powinieneś jego nazwę poprzedzać literą T.

Pola klasy

Nazwy pól klasy powinne być poprzedzone literą F - np.:

type
  TMyClass = class
  private
    FBar : String;
    FFoo : String;
  end;

Dodatkowo, jeżeli w naszej klasie korzystamy z właściwości, nazwy funkcji obsługujących daną właściwość powinny być poprzedzone frazą Set lub Get:

type
  TMyClass = class
  private
    FBar : String;
    FFoo : String;
    function GetFoo : String;
    procedure SetFoo(Value : String);
  published
    property Foo : String read GetFoo write SetFoo; 
  end;

W tym przykładzie mamy do czynienia z właściwością, która przy odczycie wartości wykorzystuje funkcję GetFoo; natomiast przy przypisywaniu wartości wywoływana jest SetFoo.

Nazewnictwo metod

Nazwy metod powinny możliwe jak najdokładniej odzwierciedlać faktycznie przeznaczenie danej Metody czy Procedury. Np. niewiele mówiąca nazwa KbdBtn może zostać zastąpiona SetKbdButton lub CallKbdButton. Stosujmy czytelne i jasne nazwy dla funkcji - np.:

function IsVisible : Boolean;
procedure ServerStart;
procedure ServerStop; 

Pozostałe

Zarówno nazwy klas, typów, modułów, rekordów powinny korzystać ze wspomnianego wcześniej stylu wielbłądziego.

Notacja węgierska

Co prawda nazewnictwo komponentów czy zmiennych nazwane notacją węgierską nie jest oficjalnym stylem kodowania w firmie Borland, aczkolwiek jest to metoda tak rozpowszechniona iż warto o niej wspomnieć. Notacja węgierska (pomysłodawca, pracownik firmy Microsoft jest węgrem) jest szeroko stosowana w Win32. Polega na poprzedzaniu nazw parametrów, zmiennych frazami oznaczającymi typ danej zmiennej. I tak w WinAPI, nazwy uchwytów poprzedzone są literą h, łańcuchy z zerowym ogranicznikiem - lp.

W tabeli poniżej znajdują się propozycje do zastosowania w przypadku typów Delphi.

FrazaTyp/element
a[[Delphi/Tablice|tablice]]
b[[Delphi/Boolean]]
ch[[Delphi/Char]]
cu[[Delphi/Currency]]
dt[[Delphi/TDateTime]]
f[[Delphi/Float]]
h[[Delphi/Handle]]
i[[Delphi/Integer]]
p[[Delphi/Pointer]]
s[[Delphi/String]]
v[[Delphi/Variant]]

Przykłady:

var
  iFoo : Integer;
  sName : String;

Komentarze

Niezwykle istotna rzecz w pracy grupowej to komentarze. Jest to praktycznie niezbędny element. W Delphi można wyróżnić dwie grupy komentarzy. Pierwsza grupa komentarzy to komentarze blokowe, ograniczone znakami { oraz } lub (* i *). Druga grupa to komentarze jednej linii, rozpoczynające się od znaków //.

Umiejętne komentowanie nie jest tak prostą rzeczą za jaką można by uważać. Komentarz powinien być krótki, a zarazem treściwy; powinien przekazywać programiście jak najwięcej informacji. Często stosujemy komentarze, które są niepotrzebne, gdyż działanie danej instrukcji jest zupełnie jasne - np.:

while I < Count do
begin
  // do wartości zmiennej I dodaj 1
  I := I + 1; 
end;

To jest oczywiste, gdyż widzimy, że wartość zmiennej I jest zwiększana o 1. Ten komentarz byłby lepszy, gdyby był zapisany w ten sposób:

while I < Count do
begin
  // dodaj wartość 1, aby nie zapętlić
  I := I + 1; 
end;

Komentarze grupowe

Komentarz grupowy powinien znajdować się na samej górze każdego modułu (pisaliśmy o tym wcześniej). Powinien zawierać informacje o przeznaczeniu danego modułu oraz o prawach autorskich. Dodatkowo komentarze grupowe powinny poprzedzać deklarację klasy oraz definicję metod:

{ TBar.Foo
  
   Metoda realizuje kod wymagany przez klasę bazową. } 

procedure TBar.Foo;
begin

end;

Właściwą treść komentarzy od znaków { i } powinna rozdzielać spacja.

Komentarze jednej linii

Komentarzy jednej linii powinno być w kodzie jak najwięcej. Powinny one opisywać działanie możliwie jak największej ilości instrukcji programu. Komentarze powinny być zapisane z tym samym wcięciem, co komentowana instrukcja:

// wywołanie metody potrzebnej do dalszego działania programu
Foo;

if X > Y then
begin
  // Przypisanie do zmiennej wartości funkcji
  Bar := Some;
  
  // kończymy działanie programu
  Exit;
end;

Treść komentarza od znaków // powinna dzielić jedna spacja. Zwróć uwagę, iż jedna linia przerwy pomiędzy komentowanymi instrukcjami może zwiększyć czytelność kodu.

Niekiedy programiści stosują specjalne oznaczenia, aby wyróżnić dany komentarz, dać innym do zrozumienia, iż jest to komentarz tymczasowy, który powinien być uzupełniony lub usunięty. Stosowane jest wówczas oznaczenie ???:

// ???: Franek, spójrz czy nie zamykamy tabeli za wcześnie?
Table1.Close;

3 komentarzy

Nie do końca prawdą jest, ze aplikacja może zostać zapisana w jednej linii. Owszem - wszystko fajnie, do czasu kiedy ilość znaków w pojedynczej linii nie przekracza 1023. W innym przypadku kompilator zgłasza błąd (Prawda na pewno w przypadku Delphi 7 i zapewne niższych. Nie wiem jak z wyższymi)

W edytorze kodu w Delphi, tę granicę określa pozioma linia edytoru (w domyślnych ustawieniach wskazuje na 80 znak).
Chyba pionowa :>