Spis treści
Składnia często postrzegana jest raczej jako aspekt "mechaniczny" programowania. Zwolennicy tej teorii argumentują, że składnią zaspokajamy potrzebę "co chcemy uzyskać", a nie "jak". To oczywiście nieprawda. W cywilizowanym świecie ważnymi czynnikami w komunikacji są: jasność i jednoznaczność.
Podobnie program komputerowy jest formą komunikacji pomiędzy piszącym a czytającym, w którym to wypadku ten drugi jest raczej wymagający, obojętne czy będzie to kompilator, inny członek zespołu, recenzent czy też jakaś inna duszyczka. W rzeczywistości, większość komunikacji tyczącej się programów zachodzi pomiędzy dwiema osobami. Czytelna i jednoznaczna składnia jest bardzo pomocna w nawiązywaniu kontaktów i — jak to zobaczymy — zapobiega wielu pospolitym błędom.
Ważnym aspektem dobrego modelu składni jest wzięcie pod uwagę faktu, iż typowe, proste błędy powstające podczas wprowadzania tekstu programu powodują, że program staje się nieprawidłowy, a tym samym jego kompilacja zostaje przerwana z komunikatem błędu, a nie, że program zostaje przyjęty przez kompilator, tyle że z nieprzewidywalnymi skutkami. Oczywiście trudno jest ustrzec się przed błędami wprowadzania typu X zamiast Y, czy + zamiast *, lecz można uniknąć wielu błędów strukturalnych. Przy okazji zauważmy, że z powyższego powodu dobrym zwyczajem jest unikanie zbyt krótkich identyfikatorów. Jeśli mamy do czynienia z oprogramowaniem finansowym obliczającym raty rozłożone w czasie, korzystanie z identyfikatorów R i T jest ryzykowne, gdyż łatwo tutaj popełnić błąd z uwagi na wzajemne położenie obu znaków na klawiaturze. Lecz jeśli jako identyfikatorów użyjemy Rate i Time, niezamierzony wpis typu Tate czy Rime zostanie wykryty przez kompilator. To tyczy się oczywiście dowolnego języka programowania.
1.1 Równość a przypisanie
Jest oczywistym dla każdego, że przypisanie i równość to dwie różne rzeczy. Jeśli dokonujemy przypisania, zmieniamy stan jakiejś zmiennej. Z kolei równość jest po prostu działaniem testującym pewien stan. Zmiana stanu i testowanie stanu to dwa bardzo różne zagadnienia, a zrozumienie tego jest niezwykle istotne.
W przypadku wielu języków programowania wprowadzono dużo zamieszania odnośnie tych fundamentalnie różnych operacji logicznych.
Dawno temu programujący w Fortranie zapisywał:
X = X + 1
I to jest właśnie osobliwe. W matematyce x nigdy nie jest równe x + 1. Instrukcja w Fortranie oznacza oczywiście „zamień bieżącą wartość X na starą powiększoną o jeden”. Ale dlaczego znak równości znalazł takie zastosowanie, skoro społeczeństwa używają go do oznaczania równości od setek lat? Przy okazji: znak równości po raz pierwszy znalazł swe zastosowanie około 1550 roku — wprowadził go angielski matematyk Robert Recorde. Projektanci Algolu 60 zauważyli problem i wykorzystali kombinację dwukropka i znaku równości do oznaczenia przypisania. Tak więc mamy:
X := X + 1;
co ma taką użyteczną konsekwencję, że znak równości może być jednoznacznie użyty do oznaczania równości:
if X = 0 then ...
Język C (podobnie jak Fortran) zaadoptował = jako przypisanie, co w efekcie wymusiło wykorzystanie sekwencji == do oznaczania równości. Jest to źródłem dużego zamętu.
Oto fragment programu w C sterującego pracą sygnalizacji na skrzyżowaniu torów kolejowych:
if (the_signal == clear) { open_gates (...); start_train (...); }
Ten sam program w Adzie mógłby wyglądać tak:
if The_Signal = Clear then Open_Gates (...); Start_Train (...); end if;
Teraz rozważmy co się stanie, gdy programista popełni błąd i przypadkowo zapomni o jednym znaku równości w przypadku języka C:
if (the_signal = clear) { open_gates (...); start_train (...); }
Fragment ten ciągle da się kompilować, lecz zamiast operacji testowania the_signal mamy operację przypisania wartości clear do zmiennej the_signal. Mało tego. Język C ujednolicił wyrażenia (mające wartość) z przypisaniami (zmieniającymi stan). W efekcie przypisanie postrzegane jest również jako wyrażenie, a efekt przypisania traktowany jest jako test. Jeśli clear jest zakodowane jako różne od 0, w rezultacie otrzymamy prawdę, co spowoduje, że bramka będzie zawsze otwarta, a pociąg pojedzie w dosyć — delikatnie mówiąc — niebezpieczną podróż. I odwrotnie: jeśli clear będzie zakodowane jako 0, bramka pozostanie zamknięta, a pociąg będzie zablokowany. W każdym z tych przypadków sprawy mają się nie za dobrze.
Pułapki związane z wykorzystaniem "=" jako przypisania i "==" jako równości oraz zezwolenie na traktowanie przypisań jako wyrażeń są bardzo dobrze znane społeczności używającej języka C i zmusza do tworzenia zasad kodowania oraz narzędzi analizujących kod takich jak np. lint. Jednakże lepiej jest unikać takich pułapek na samym początku drogi tworzenia programu, a to na przykład przez odpowiedni projekt języka, która to kwestia w przypadku Ady jest już rozwiązana.
Jeśli programujący w Adzie przypadkowo użyje przypisania w teście:
if The_Signal := Clear then -- niedozwolone
kompilacja programu się nie powiedzie i wszystko będzie jak należy.
1.2 Grupy instrukcji
Często zachodzi konieczność zgrupowania sekwencji instrukcji, np. poniższy test wykorzystujący słowo kluczowe if. Są dwie metody na uczynienie tego:
- poprzez ograniczenie grupy instrukcji nawiasami klamrowymi, dzięki czemu traktowane są one jako jedno kompletne działanie (jak w języku C),
- poprzez zamknięcie sekwencji czymś uzupełniającym słowo kluczowe if (jak w Adzie).
Zilustrujemy to znanym nam przykładem z życia kolei. Instrukcje otwierające bramkę i powodujące start pociągu mogą nastąpić tylko wtedy, gdy warunek spełnia określone wymagania.
W C mamy coś takiego:
if (the_signal == clear) { open_gates (...); start_train (...); }
Przypuśćmy, że przez nieuwagę dodamy średnik na końcu pierwszego wiersza (łatwo to uczynić). Program ma postać:
if (the_signal == clear); { open_gates (...); start_train (...); }
Widzimy teraz, że warunek ma wpływ jedynie na instrukcję pustą (null), która jest niejawnie wprowadzona pomiędzy test a nowo wprowadzony znak średnika. Nie możemy tego zobaczyć, ponieważ instrukcja pusta nie robi nic. Tak więc bez względu na warunek, bramki są zawsze otwarte, a pociągi jeżdżą bez żadnych ograniczeń.
W Adzie odpowiadający powyższemu błąd da w rezultacie:
if The_Signal = Clear then ; -- niedozwolone Open_Gates (...); Start_Train (...); end if;
Powyższy fragment składniowo jest nieprawidłowy, tak więc błąd taki będzie "wychwycony" przez kompilator, a zderzenie pociągów z tego powodu nie nastąpi.
1.3 Notacja nazwana
Inna cecha Ady natury składniowej ułatwiająca wykrywanie wielu błędów to notacja nazwana (named notation). Daty są dobrym przykładem do zilustrowania zalet stosowania tej notacji, gdyż porządek ich składowych zależy w dużej mierze od zwyczajów (lub norm) narodowych. Dla przykładu, 12 stycznia 2008 w Europie zapisywany jest w postaci 12/01/08, w Stanach — zazwyczaj jako 01/12/08, norma ISO zaś narzuca format, w którym rok znajduje się na pierwszym miejscu: 08/01/12.
W C można by zadeklarować taką strukturę do manipulowania datami:
struct date { int day, month, year; }
co odpowiada poniższej deklaracji typu w Adzie:
type Date is record Day, Month, Year : Integer; end record;
W C zapisujemy:
struct date today = {1, 12, 8};
Tyle, że bez wglądu w deklarację typu nie możemy stwierdzić czy oznacza to: 1 grudnia 2008 roku, 12 stycznia 2008 roku czy też 8 grudnia 2001 roku.
W Adzie mamy możliwość zapisu:
Today : Date := (Day => 1, Month => 12, Year => 08);
Wykorzystujemy tutaj przypisanie nazwane. Teraz mamy jasność co do daty, nawet jeżeli zapiszemy liczby w innej kolejności. Przy okazji zauważmy, że Ada zezwala na używanie zer wiodących.
Możemy również zapisać deklarację w postaci:
Today : Date := (Month => 12, Day => 1, Year => 08);
co nadal ma prawidłowe znaczenie i ujawnia tę zaletę, że nie musimy pamiętać w jakiej kolejności pola są zadeklarowane.
Przypisanie nazwane może być również użyte w innych kontekstach. Podobnie błędne sytuacje mogą powstać także w przypadku funkcji mających kilka parametrów tego samego typu.
Przypuśćmy, że mamy funkcję obliczającą wskaźnik otyłości osoby. Dwoma parametrami tej funkcji są: wzrost i waga. Podane są one jako wartości zmiennoprzecinkowe odpowiednio w calach i funtach (lub w metrach i kilogramach, jeśli korzystamy z metrycznego systemu miar). Tak więc w C będziemy mieli:
float index (float height, float weight) { return ... }
zaś w Adzie:
function Index (Height, Weight : Float) return Float is begin return ...; end Index;
Teraz odpowiednie wywołanie funkcji obliczającej wskaźnik (dane dotyczą autora...) w C wyglądać może tak:
my_index = index (68.0, 168.0);
Przez pomyłkę zamieniamy parametry:
my_index = index (168.0, 68.0);
W efekcie otrzymamy bardzo cienkiego, ale za to bardzo wysokiego giganta! Ciekawą okolicznością jest fakt, że obie wartości kończą się ciągiem 68.0...
Takiej niezdrowej sytuacji można uniknąć w Adzie dzięki wykorzystaniu nazwanych parametrów wywołania:
My_Index := Index (Height => 68.0, Weight => 168.0);
Możemy znowu zamienić parametry miejscami. Nie spowoduje błędu to, że nie pamiętamy kolejności parametrów funkcji.
Notacja nazwana jest bardzo wartościową cechą Ady. Korzystanie z niej jest opcjonalne, lecz można jej używać do woli, gdyż nie tylko pomaga unikać błędów, ale również czyni program łatwiejszym do zrozumienia.
| Wprowadzenie | Rozdział 2: Bezpieczny system typów |
| Tekst oryginalny w języku angielskim - |
|



