Olsztyn 2006-2012 Wojciech Sobieski
Jedną z podstawowych zasad obowiązujących podczas pisania programów jest czytelność i przejrzystość kodów źródłowych. Dobrze napisany kod umożliwi innym programistom (a często i autorowi) późniejsze ich zrozumienie i usprawnienie. Jest to bardzo ważne gdyż raczej rzadko zdarza się napisać od razu program w wersji ostatecznej, nie wymagającej poprawek i usprawnień. Najczęściej dzieje się wręcz odwrotnie tworzy się mały program a następnie nieustannie go ulepsza. Istnieje pewna ilość zaleceń i schematów, których przestrzeganie wpłynie na poprawność stylu pisanego kodu źródłowego. Należy jednak pamiętać, że najważniejszy jest efekt działania programu (a nie sam program) i w uzasadnionych przypadkach możne dojść do pewnych odstępstw od przyjętych reguł.
Cechy charakteryzujące dobry program: poprawność (zgodność z wymogami użytkownika), niezawodność (dobre dane wejściowe dobre wyniki), przenośność (łatwość instalacji na różnych komputerach), łatwość konserwacji (prosto napisany program łatwo przystosować do różnych warunków pracy), czytelność (prostota jest jedną z najważniejszych cech dobrych programów), prawidłowe wykorzystanie zasobów (pamięci, dyski, itp.), szybkość.
Styl programowania obejmuje: nazwy zmiennych w programie, wielkości stałe, liczba zmiennych globalnych, deklaracje typów zmiennych, komentarze, puste linie, odstępy poziome, akapity, wcięcia, itd., podział na sekcje, podprogramy, uniwersalność i standaryzacja modułów, funkcjonalność i wygoda użytkowników, formułowanie celów, złożoność systemu.
1. Nazwy zmiennych. Wybór nazw zmiennych ma pomóc w zrozumieniu ich znaczenia i zastosowania. Najlepiej jest używać nazw, które coś oznaczają, powiadamiając tym samym użytkownika (intencjonalnie) o możliwościach funkcji czy procedury, czy też wskazując sens zmiennych, itd. Z reguły języki programowania nie narzucają nazewnictwa zmiennych. Nazwy można tworzyć opierając się na typach zmiennych (np. wszystkie zmienne liczb zespolonych mogą mieć nazwę zaczynającą się od ZESP_ ), obszarach obowiązywania (np. zmienne obowiązujące w całym programie zaczynają się od GLOBAL_, a obowiązujące w podprogramie, procedurze lub funkcji od LOCAL_ ) lub ich funkcjach (np. zmienne sterujące są inaczej nazywane niż zmienne obliczeniowe, itd.).
Dobrze jest stosować rzeczowniki dla oznaczania nazw wszelkich danych, czasowniki dla funkcji i procedur (rozkazy, np. dziel, dodaj itp), przymiotniki dla zmiennych logicznych (w Pascalu boolean). Generalnie można tworzyć dowolne, byle przejrzyste, zasady nazywania zmiennych ważne jest jedynie aby te zasady przestrzegać. Zbyt długie nazwy można zastąpić ich skrótami (np. zamiast temperaturamaksymalna tempmax lub temp_max, itp.). Jeżeli translator nie rozróżnia małych i dużych liter, powinno się pisać duże litery na początku każdego słowa (np. nazwa maksymalnatemperaturaroczna jest mniej przejrzysta niż MaksymalnaTemperaturaRoczna) - jeszcze lepiej byłoby zastosować skrót, np. TmaxRok. Tworzone skróty powinny być standardowe dla danej instytucji lub dziedziny.
Reguły skracania nazw: skraca się każde znaczące słowo w nazwie, pierwsza litera musi wystąpić, spółgłoski są ważniejsze od samogłosek, początek słowa jest ważniejszy niż koniec, tworzy się skróty na 6 do 15 liter. Z reguł tych wynika następujący algorytm: usuwa się od końca najpierw samogłoski potem spółgłoski, oprócz pierwszej litery, aż do osiągnięci pożądanej długości. Porządkowanie list zmiennych wg alfabetu znacznie ułatwia ich odszukiwanie i określenie typu.
Przykłady poprawnie dobranych nazw danych (Pascal): P, Ro : Real; Vx, Vy : Real; MaxIter : Integer; Przykłady niepoprawnie dobranych nazw danych (Pascal): Dana1, Dana2 : Real; x, y : Real; n : Integer;
2. Wielkości stałe. Z wyjątkiem zera, jedynki i może jeszcze kilku innych stałych, nie należy wprost używać stałych w całej treści programu, w jego wnętrzu. Najlepiej zadeklarować (zgłosić) je w części opisowej, specjalnie do tego przeznaczonej, np. za pomocą słowa Pascala const. Zmiana sprzętu często powoduje idące za nią zmiany przybliżonych wartości różnych stałych. Ile czasu zmarnujemy wyszukując w programie miejsca, w których występuje stała zapisana jako liczba zmiennoprzecinkowa z dokładnością do 9 miejsc znaczących by zamienić ją na liczbę o 12 miejscach znaczących.
Przypuśćmy, że w programie używamy tablicy, której rozmiar 100 zgłosiliśmy kilkakrotnie pisząc między innymi ciąg znaków [1..100]. Załóżmy, że zmienimy wymiar 100 wszędzie, automatycznie, za pomocą edytora tekstu, na 200, a przy okazji, całkiem nieświadomie, zamienimy temperaturę jakiegoś procesu też na 200, ale stopni. Zastosowanie pliku dołączalnego do definicji podstawowych parametrów programu (Fortran).
3. Zmienne globalne. Należy minimalizować liczbę zmiennych globalnych, dostępnych z całego programu. Zmiany w procedurach czy funkcjach, których jest wiele w programach, są zmianami lokalnymi. Często zapominamy przy nich o zmiennych globalnych. W ten sposób łatwo o błędy. Zmienne globalne Zmienne lokalne Zmienne lokalne moduł 1 moduł 2
4. Deklaracje typów zmiennych. Wszystko, co tylko się da, należy umieszczać w deklaracjach typu, włączając w to podzakresy, definicje tablic oraz rekordów. Należy unikać deklarowania niejawnego. Źle: Dobrze (n przyjmie wartość 6,23): x=2.45 y=3.78 n=x+y write(*,*) n W tym przypadku n przyjmie wartość 6 ponieważ domyślnym typem zmiennych o nazwach i,j,k,l,m,n jest typ INTEGER. Real x,y,n x=2.45 y=3.78 n=x+y write(*,*) n
5. Komentarze. Są to teksty nie podlegające translacji, służące do opisu kodu źródłowego. Mają one mówić jak rozumieć program, ale nie opisywać drugi raz tego co wynika z kodu (np. komentarz tutaj pod zmienną x podstawiamy 7, uczyniony w miejscu x: = 7 jest niepoprawny). Powinny zawierać logiczny sens zastosowania danej sekwencji instrukcji, o ile nie jest on oczywisty podczas lektury samego kodu (nadmiar komentarzy może też zatracić przejrzystość samego kodu!). Najczęściej objaśnia się ważniejsze pętle, skoki, sprawdzanie warunków, itp. Komentarze muszą być przynajmniej równie czytelne jak kod, powinny poprawiać czytelność kodu, dlatego powinny mieć tę samą szerokość wcięcia co kod, a mogą też być wyróżnione znakami specjalnymi, ramkami, itp.
Na początku programu powinien znaleźć się komentarz wstępny, zawierający: opis działania programu, sposób użycia - jak wywołać program, listę i opis ważniejszych zmiennych, opis plików WE/WY, nazwy używanych podprogramów, nazwy wszelkich specjalnych metod, które zostały użyte, wraz ze wskazaniem, gdzie można znaleźć dalsze informacje, informacje o czasie działania (jeśli ma to znaczenie), wymagania sprzętowe i systemowe, opis specjalnych poleceń dla operatora / użytkownika, informacje o autorach i kontaktach, datę napisania (ew. datę ostatniej modyfikacji lub numer wersji).
6. Puste linie. Oddzielają poszczególne fragmenty programu ułatwiając późniejsze ich poszukiwanie. Puste linie (jedna, dwie) POWINNY! oddzielać różne fragmenty programu i podprogramy. Źle: Dobrze: Real x,y,z x=2.45 y=3.78 z=x+y write(*,*) z Real x,y,z x=2.45 y=3.78 z=x+y write(*,*) z
7. Odstępy poziome (spacje). Zwiększają czytelność kodu, w szczególności zaś wyrażeń arytmetycznych, list parametrów i itp. Źle: if(z.lt.0)then print '($A25)',(wartosc ujemna)' elseif(z.eq.0)then print '($A25)',(wartosc zerowa)' else print '($A25)',(wartosc dodatnia)' end if Dobrze: if (z.lt. 0) then print '($A25)', (wartosc ujemna)' else if (z.eq. 0) then print '($A25)', (wartosc zerowa)' else print '($A25)', (wartosc dodatnia)' end if
8. Wcięcia. Odzwierciedlają wzajemne powiązania instrukcji i grup instrukcji, uzewnętrzniają logiczną strukturę programu (lub danych). Źle: case n of 1: begin if i=j then k:=1 else k:=0; end; 2: begin if i=j then k:=0 else k:=1; end; end; Dobrze: case n of 1: begin if i=j then k:=1 else k:=0; end; 2: begin if i=j then k:=0 else k:=1; end; end;
9. Akapity. Akapitów używamy w instrukcjach złożonych. Należy do dobrego tonu podpatrzenie jak radzą sobie z tym inni. Decydując się na jakiś styl trzymajmy się go zawsze. Nie mieszajmy różnych stylów. Programy tracą wtedy na czytelności. Z czasem praktyka pokaże, że trzymanie się jednego stylu opłaca się. Tymczasem należy się do tego przyzwyczajać. Źle: Dobrze: print '($A10)', ' Podaj x: ' read(*,*) x write(*,*) ' x = ', x print '($A10)', ' Podaj i: ' read(*,*) i write(*,*) ' i = ', i print '($A13)', ' Podaj text: ' read(*,*) text write(*,*) ' text = ', text print '($A10)', ' Podaj x: ' read(*,*) x write(*,*) ' x = ', x print '($A10)', ' Podaj i: ' read(*,*) i write(*,*) ' i = ', i print '($A13)', ' Podaj text: ' read(*,*) text write(*,*) ' text = ', text
10. Numeracja linii (w językach których to dotyczy). Numeracja powinna być prowadzona co 10, aby można było zawsze coś wstawić w środek. Źle: 1 write(*,'(a)') ' a < 0' goto 4 2 write(*,'(a)') ' a = 0' goto 4 3 write(*,'(a)') ' a > 0' goto 4 4 continue end Dobrze: 10 write(*,'(a)') ' a < 0' goto 40 20 write(*,'(a)') ' a = 0' goto 40 30 write(*,'(a)') ' a > 0' goto 40 40 continue end
11. Przenoszenie słów. Nie jest zalecane. Jeżeli już jest to konieczne, to linia powinna się kończyć tak, by było widać, że logicznie nie jest zakończona i że jej kontynuacja musi następować dalej (np. w wyrażeniach arytmetycznych ostatni w linii powinien być operator, a nie operand). Źle: Dobrze: z=(2/3)*sin(x*x+y*y) +-(1/3)*cos(x*x+y*y) z=(2/3)*sin(x*x+y*y)- +(1/3)*cos(x*x+y*y)
12. Rozmieszczenie instrukcji. Jest jednym z najważniejszych elementów stylu. Powinny one być od siebie odseparowane (ale nie za bardzo), najlepiej gdy będzie jedna instrukcja w linii. Źle: case n of 1: begin if i=j then k:=1 else k:=0; end; 2: begin if i=j then k:=0 else k:=1; end; end; Dobrze: case n of 1: begin if i=j then k:=1 else k:=0; end; 2: begin if i=j then k:=0 else k:=1; end; end;
13. Nawiasy. Używanie nawiasów jest bardzo zalecane, szczególnie przy długich wyrażeniach arytmetycznych. W takim przypadku błędna formuła obliczeniowa (brak lub złe miejsce wstawienia nawiasu) może być przyczyną trudnych do wykrycia błędów.
14. Sekcje. W przypadku dużych programów należy je dzielić na elementy według następujących zasad: algorytm programu należy dzielić na pojedyncze zadania o ściśle określonym celu. Uwaga programisty skupia się wówczas na konkretnym problemie, nie związanym bezpośrednio z resztą programu, zmniejsza to znacznie prawdopodobieństwo popełnienia błędu. Poszczególne segmenty mogą być oddzielnie testowane, co ułatwia wyszukiwanie i usuwanie błędów w całym programie. W przypadku dużych programów, poszczególne moduły mogą być ponadto pisane przez różnych programistów. konkretne zadanie powinno być w programie rozwiązane tylko raz. Jeżeli to tylko możliwe nie należy mieszać i powielać zadań z różnych segmentów może to znacznie utrudnić znajdowanie błędów: nie wiadomo bowiem który segment jest odpowiedzialny za powstanie błędu.
segmenty powinny być uniwersalne. Oznacza to, że należy je tak budować, aby dało się je wykorzystać w innych programach. Jest to szczególnie przydatne, gdy programista tworzy aplikacje z określonej dziedziny i często musi rozwiązywać podobne problemy. Korzystanie z gotowych i przetestowanych segmentów może znacznie przyspieszyć i ułatwić pracę. każdy segment powinien realizować swoje zadania niezależnie od innych segmentów. należy dążyć do tego, aby jak najwięcej użytych w programie zmiennych miało zasięg lokalny (czyli obowiązywały w obrębie podprogramów, procedur i funkcji). Znacznie łatwiej jest wówczas kontrolować zawartość zmiennych, a przypadkowa i nieprzewidziana zmiana ich wartości jest mniej prawdopodobna.
15. Uniwersalność. Można ją osiągnąć przez stosowanie: parametryzacji, używanie standardowych bibliotek, standaryzację postaci wejścia-wyjścia, stosowanie standardów stylu.
Parametryzacja oznacza niezależność od konkretnego zestawu danych, może być osiągnięta przez parametryzację. Pozwala to na łatwe i szybkie zmiany w programie, dlatego najczęściej używane parametry to: wymiary tablic i list, dane specjalne, np. stopa procentowa, podatkowa, desygnatory urządzeń WE/WY (też ścieżki dostępu i nazwy plików). By zmienić program bez parametryzacji trzeba ręcznie pozmieniać wszystkie wystąpienia danej wartości, ale można łatwo coś przeoczyć lub zmienić coś, co ma taką samą wartość, ale odnosi się do innej zmiennej.
Używanie standardowych bibliotek, funkcji i procedur. Są to elementy już zoptymalizowane i przetestowane, przez co zapewniają lepszą efektywność i mniejsze prawdopodobieństwo wystąpienia błędu. Oszczędność na czasie kodowania też jest olbrzymia.
Standaryzacja postaci WE/WY musi być szczegółowo zaprojektowana przed kodowaniem. Najlepszy jest naturalny i jednorodny układ zmiennych i formatów.
Standardy stylu. Standard stylu jest to zbiór określonych zasad pisania programów. Standardy mogą być narzucone z góry lub mogą być tworzone indywidualnie. Mają tę zaletę, że zmniejsza się możliwość nieporozumienia, jeżeli robi się tę samą rzecz za każdym razem tak samo. Potrzebny jest tylko niewielki wysiłek, by przestrzegać standardów stylu. Standardy mogą ograniczać przyszły rozwój i postęp, być zbyt krępujące lub nieporęczne. Dobre standardy nie posiadają tych cech (lecz i tak istnieją rzadkie przypadki, w których można nie zastosować się do standardu). Instytucja narzucając standardy stylu sprawia, że powstające tam programy będą jej własnością, a nie poszczególnych programistów.
16. Funkcjonalność i wygoda użytkowników jest obecnie popularnym hasłem zawartym w pojęciu User friendly program. Oznacza to, że użytkownik programu (operator) powinien mieć możliwość korzystania z programu w sposób naturalny, bez specjalnego przygotowania. Program powinien przewidywać zadania użytkownika i służyć mu podpowiedziami i pomocą, nie dopuszczając jednocześnie do wywołania błędu użytkownika.
17. Formułowanie celów. Musi się dokonać na początku projektowania (różne cele mogą być konfliktowe, np.: duża niezawodność, dotrzymanie terminów, minimalny koszt lub czas produkcji, efektywność w użyciu pamięci /szybkości, możliwość późniejszej modyfikacji, uniwersalność, prostota/ łatwość obsługi i konserwacji. Podstawową zasadą obowiązującą podczas formułowania celów jest zasada skromności : Skromny, działający program jest bardziej użyteczny niż program imponujący, ale niedokończony.
18. Złożoność. Złożoność wpływa na trudności w testowaniu, uruchamianiu i konserwacji. Mniej złożony produkt znacznie łatwiej opanować. Ograniczenie złożoności osiąga się dzięki dzieleniu programu na moduły i projektowaniu zstępującemu, co zmniejsza liczbę poziomów rozumienia. Moduły zmniejszają liczbę możliwych dróg w programie (więc łatwiej dokonywać zmian nie wpływających na inne elementy programu) oraz izolują błędy wewnątrz modułu.
Dziękuję za uwagę Olsztyn 2006-2012 Wojciech Sobieski