Inżynieria oprogramowania

Podstawowe pojęcia, niezależnie od języka

  • 2008-10-19 20:58
  • 7 komentarzy
  • 4812 odsłon
  • Oceń ten tekst jako pierwszy
Spis treści

     1 Wstęp
     2 Do czego służy język programowania
     3 Słowo Kluczowe języka
     4 Zmienna
     5 Tablica i Zbiory
     6 Instrukcja Warunkowa
     7 Pętla
          7.1 pętla for
          7.2 pętle while i do while
          7.3 Przerwanie wykonania i przejście do kolejnego kroku
          7.4 Iterator
     8 Przełącznik
     9 Skok
     10 Jak działa pamięć
     11 Pojęcie wejścia i wyjścia
     12 Funkcja
     13 Obiekty, klasy, interfejsy
     14 Podsumowanie


Wstęp


W artykule omawiającym problem wyboru języka omówione zostały cechy większości najpopularniejszych języków. Autor przedstawił też problemy, które stają przed nami gdy chcemy wybrać jakiś język. Omówił też czym należy kierować się przy wyborze języka.
Jednak wybór języka to jedna strona medalu. Drugą jest zbiór pojęć i definicji, z którymi spotkamy się niezależnie od wybranego języka.

Do czego służy język programowania


Języki programowania pozwala na bardziej przyjazne dla człowieka porozumiewanie się z komputerem. Dzięki nim możemy pisać w sposób prawie naturalny, a dopiero kolejne narzędzia takie jak kompilatory, translatory, interpretery tłumaczą to na język maszynowy, a ten zostaje zamieniony na sygnał elektryczny. Niektóre języki opracowano do ogólnych zastosowań, inne do bardzo wyspecjalizowanych zadań. Przykładem tego ostatniego niech będzie SQL, który służy tylko i wyłącznie do pracy z relacyjnymi bazami danych i nie nadaje się do niczego innego. Przykładem języka ogólnego może być C++ lub Java. Warto też wspomnieć tu o pojęciu Frameworku inaczej szkieletu.  Jest to zazwyczaj zbiór narzędzi ułatwiających tworzenie programów pewnego typu. Doskonałymi przykładami są Ruby on Rails lub Spring Framework.

Słowo Kluczowe języka


Słowo kluczowe w programowaniu ma takie samo znaczenie jak w mowie. Oznacza ono elementarną część mowy. Jeżeli przyjmiemy, że z komputerem rozmawiamy za pomocą języka programowania, tak jak z innym człowiekiem, to słowa kluczowe stanowią zbiór słów, które mają specjalne znaczenie dla komputera. Oznaczają one podstawowe instrukcje, bloki instrukcji oraz polecenia, które ma wykonać komputer. Co do zasady słowa kluczowe są zastrzeżone i nie mogą byś używane jako nazwy zmiennych, klas, interfejsów, metod, funkcji.

Zmienna


Pierwszym pojęciem z jakim spotykamy się w prawie każdym języku programowania jest pojęcie zmiennej. Zmienna reprezentuje jakiś obszar w pamięci komputera. Jest przyjazną dla programisty nazwą takiego obszaru. Zamiast pisać bezpośrednio w "języku komputera" wskazując interesujący nas obszar pamięci, wystarczy podać bardziej przyjazną dla człowieka nazwę.
Dobrą praktyką związaną z używaniem zmiennych jest nadawanie im znaczących nazw. Jeżeli zmienna ma reprezentować na przykład liczbę osób w pokoju to warto nazwać ją liczbaOsóbWPokoju, a nie r2d2. Niby nie ma to znaczenia (kompilator i tak zamieni to na niezrozumiały ciąg), ale później kod łatwiej się czyta i poprawia.
Praca ze zmienną zazwyczaj składa się z dwóch etapów. Pierwszym jest jej deklaracja, czyli pierwsze pojawienie się nazwy zmiennej w kodzie, które może być poprzedzone odpowiednim słowem kluczowym oraz deklaracją typu. Drugim jest nadanie zmiennej wartości, czyli przypisanie jej jakiegoś obszaru pamięci. Zazwyczaj polega to na użyciu konstrukcji:
 [SŁOWO_KLUCZOWE] [TYP] ZMIENNA = wartość

W niektórych językach istnieje możliwość dynamicznej deklaracji zmiennej. Oznacza to, że w dowolnym miejscu programu można zadeklarować zmienną, a następnie nadać jej wartość (w tym samym miejscu lub później). W innych językach wymagana jest deklaracja wszystkich zmiennych na początku programu. Potem nadaje się im wartość.

Tablica i Zbiory


Specyficznymi rodzajami zmiennych są tablice i zbiory. Tablica to taki rodzaj zmiennej, która składa się z komórek przechowujących dane. Dostęp do komórek odbywa się na podstawie indeksu, który może być liczbą lub jakąś daną (tablica asocjacyjna) - kluczem. Odpowiednikiem tablicy w matematyce jest macierz. Zbiory są w przeciwieństwie do tablic nieuporządkowane. Zmienna zbioru reprezentuje zazwyczaj grupę pewnych danych, a jej podstawowym zadaniem jest ułatwienie i usystematyzowanie dostępu do tych danych.

Instrukcja Warunkowa


Jak działa komputer? Chcąc w bardzo prosty sposób odpowiedzieć na to pytanie musimy wiedzieć jakie operacje jest w stanie wykonać komputer. Na pierwszy rzut oka liczba tych operacji wydaje się ogromna, ale wszystkie one sprowadzają się do kilku podstawowych operacji arytmetycznych dodawania, odejmowania, mnożenia i dzielenia. Jednak operacje te przeprowadzane są w systemie dwójkowym. Co z tego wynika? Otóż każdą z tych operacji możemy przedstawić jako pewną operację logiczną. Jeżeli mamy dwa bity to:
Dodawanie jest prostą sumą logiczną znaną pod nazwą alternatywy. Używa ona zwrotu LUB.
Odejmowanie jest alternatywą wykluczającą ALBO.
Mnożenie jest to koniunkcja I. Dla liczb wielobitowych mnożenie jest zazwyczaj przeprowadzane tak jak w szkole, czyli "pod kreskę".
Dzielenie nie ma swojego odpowiednika wśród prostych operacji matematycznych, a najprostszym algorytmem wykorzystywanym przy tej operacji jest odejmowanie od dzielnej dzielnika do momentu aż pozostanie reszta mniejsza od dzielnika.
Bardzo proste, wręcz banalne, co zatem jest niezwykłego w komputerze? Otóż komputer potrafi wykonywać dziesiątki milionów takich prostych operacji na sekundę. Zatem możemy powiedzieć, że przewyższa przeciętnego człowieka nie umiejętnościami matematycznymi, ale prędkością.

Po tym wstępie czas przyjrzeć się jeszcze jednemu mechanizmowi. Komputer poza operacjami arytmetycznymi potrafi przeprowadzić też najprostszą operację logiczną, czyli porównanie dwóch wartości. Dochodzimy tu do pierwszej złożonej konstrukcji, używanej w każdym języku programowania.

Jeżeli komputer potrafi porównać dwie wartości to potrafi też za podstawie wyniku porównania wybrać dalszą ścieżkę obliczeń.

Powyższe zdanie ilustruje jak wygląda prosta instrukcja warunkowa. Pamiętacie konstrukcję "Jeżeli A to B" (implikacja)? Tak właśnie wygląda najprostsza instrukcja warunkowa.  Oczywiście języki programowania pozwalają na urozmaicenie jej do postaci "Jeżeli A to B w przeciwnym wypadku C" lub też na ciąg instrukcji " Jeżeli A to B w przeciwnym wypadku, jeżeli C do D w przeciwnym wypadku E".
Instrukcja warunkowa zazwyczaj może być zagnieżdżania. W prawie każdym języku odpowiada jej blok kodu podobny do tego:

IF warunek [THEN]
  ciąg instrukcji jeżeli warunek jest prawdziwy
[ELSE] [ELSE IF]
  ciąg instrukcji jeżeli warunek nie jest prawdziwy
[END]

słowa ujęte w [ i ] nie są obowiązkowe lub są rzadko spotykane.

Pętla


Jeżeli mamy do wykonania jakieś powtarzające się zadanie to możemy zaprogramować je w najprostszy sposób poprzez wielokrotne powtórzenie kodu. Taka metoda jest nieefektywna, mało wydajna i czasochłonna. Z tego też powodu pojawiła się konstrukcja pętli. W prawie każdym języku jest obecna pod dwoma postaciami.

pętla for


Najprostsza z pętli jest pętla for. Jej ogólny schemat wygląda zazwyczaj w ten sposób:

FOR (ilość_razy){
  <<ciąg_instrukcji_do_wykonania>>
}[END]


Warto zwrócić tu uwagę na ilość_razy. Co do zasady pętla for powinna być stosowana wszędzie tam gdzie ilość kroków jest policzalna lub łatwa do wyznaczenia. To ostatnie pojęcie należy rozumieć jako "wszędzie tam, gdzie w chwili pisania kodu nie znamy ilości powtórzeń, ale wiemy jak je wyznaczyć" na przykład ilość wszystkich elementów w tablicy lub zbiorze. Jednak niektóre języki dopuszczają zapis, w którym nie znamy ilości powtórzeń. Wynika to z faktu, że sama instrukcja ilość_razy składa się zazwyczaj z trzech elementów:

(licznik; warunek_zakończenia; krok)


  • licznik jest to zmienna, w której zachowany jest aktualny numer kroku.
  • warunek_zakończenia to warunek logiczny mówiący, że licznik musi być mniejszy bądź równy jakiejś wartości
  • krok oznacza o ile zwiększamy licznik po każdym zakończeniu obroku pętli.

Należy uważać na możliwość stworzenia pętli nieskończonej. Pojawi się ona wtedy, gdy warunek_zakończenia będzie zawsze prawdziwy na przykład:

niech licznik zaczyna się od 1, warunek_zakończenia niech będzie "zakończ działanie gdy licznik równy 2", a krok niech wynosi 3.

W takim wypadku po pierwszym przejściu pętli licznik zostanie zwiększony o 3 i będzie wynosił 4, co spowoduje, że warunek nie będzie nigdy spełniony (co do zasady krok nie może być modyfikowany w trakcie wykonywania pętli).

pętle while i do while


Jeżeli nie wiemy ile razy należy wykonać jakąś czynność oraz nie umiemy estymować tej wartości lub chcemy, aby dana czynność była wykonywana aż nie zostanie spełniony określony warunek logiczny, powinniśmy użyć pętli while lub do - while. Schemat tych pętli jest następujący:

WHILE (warunek){
   <<ciąg_instrukcji>>
}[END]
 
DO{
   <<ciąg_instrukcji>>
}WHILE(warunek)


Sposób działania tych pętli jest bardzo prosty. ciąg_instrukcji jest wykonywany do momentu, aż warunek nie stanie się fałszywy. Podstawowa różnica polega na momencie sprawdzenia warunku. W pętli while odbywa się to przed wykonaniem kroku, a w pętli do - while po wykonaniu kroku. Oznacza to, że nie zależnie od prawdziwości warunku pętla do - while zostanie wykonana co najmniej raz.

Przerwanie wykonania i przejście do kolejnego kroku


Jeżeli w jakimś przypadku chcemy przerwać działanie pętli należy użyć słowa kluczowego BREAK ( jest to najpopularniejsza nazwa tej instrukcji ).
Jeżeli z jakiś przyczyn chcemy zaprzestać wykonywania kodu w danej iteracji i przejść do następnej należy użyć słowa CONTINUE  ( nazwa jak wyżej ).

Iterator


W niektórych językach występuje konstrukcja zwana iteratorem. Jest to, w uproszczeniu, rodzaj pętli działającej na zasadzie "Dla każdego elementu ze zbioru X wykonaj". Zazwyczaj tak działające pętle rozpoznajemy po słowie kluczowym FOREACH lub konstrukcji FOR IN. Przykładowy kod reprezentujący oba podejścia:
FOREACH( a IN collection){
   // wykonujemy działania na elemencie a ze zbioru collection
}[END]
 
//**//
 
FOR( a IN collection){
   // wykonujemy działania na elemencie a ze zbioru collection
}[END]

czasami zamiast słowa IN używany jest :.


Przełącznik


Przełącznik jest najbardziej skomplikowaną instrukcją sterującą. W ogólnej postaci wygląda w następujący sposób:
SWITCH <<zamienna>>{
[    CASE <<przypadek1>>:
         <<ciąg_instrukcji>>
         [BREAK]
]
[    CASE <<przypadek2>>:
         <<ciąg_instrukcji>>
         [BREAK]
]
     DEFAULT:
         <<ciąg_instrukcji>>
         [BREAK]
}


Sposób działania jest bardzo prosty, a jednocześnie cała "magia" powoduje wiele błędów związanych z logiką działania programu. Przełącznik działa w następujący sposób. Na podstawie zmienna jest wykonywany ciąg_instrukcji zawarty po słowie kluczowym CASE za którym występuje przypadek równy co do wartości zmienna. Następnie może się pojawić słowo BREAK oznaczające przerwanie wykonywania instrukcji i opuszczenie bloku przełącznika. Brak tego słowa spowoduje dalsze przetwarzanie instrukcji niezależnie od tego czy wartość zmienna odpowiada kolejnym warunkom. Najpopularniejszym błędem jest właśnie pominięcie słowa BREAK co powoduje nieoczekiwane zachowanie programu. Kod po słowie kluczowym DEFAULT jest wykonywany gdy nie zostanie odnaleziona żadna wartość pozwalająca na wykonanie kodu powiązanego z którymś z przypadków.

Skok


W niektórych, szczególnie starszych, językach programowania mamy do czynienia z instrukcją skoku. Instrukcja ta składa się z dwóch elementów. Pierwszy to etykieta określająca miejsce w kodzie. Drugi to instrukcja nakazująca przejście do miejsca wskazanego przez etykietę i kontynuowanie wykonania programu od tego miejsca.
[LABEL] ETYKIETA:
 
//... kod
 
GOTO ETYKIETA


Słowo GOTO jest słowem kluczowym. Nazwa etykiety nie może być słowem kluczowym.

Jak działa pamięć


Pamięć komputera najprościej można omówić przyrównując ją do skrytek pocztowych. Każda pojedyncza jednostka pamięci posiada swój adres i właściciela, czyli program do którego jest przypisana. Nie będę tu omawiał różnic w budowie pamięci, bo ta jest uzależniona w swojej logicznej warstwie od systemu operacyjnego. Skupmy się na tym, jak z pamięci korzystają programy.
Program w momencie uruchomienia otrzymuje do dyspozycji pewną ilość pamięci. Możemy ją w dowolny sposób zagospodarować. W pamięci umieszczane są wszystkie zmienne związane z programem jak i sam program. Najistotniejszym elementem jest zrozumienie jak działa powiązanie pomiędzy zmiennymi i pamięcią. Zmienna wskazuje na pewien obszar pamięci. Korzystając ze zmiennej możemy zrobić to na dwa sposoby. Pierwszy możemy zażądać wartości zmiennej. W takim wypadku tworzona jest nowa zmienna do której kopiowana jest wartość zmiennej, którą żądamy. Drugim podejściem jest żądanie wskaźnika do obszaru pamięci wskazywanej przez zmienną. W takim przypadku dwie zmienne wskazują na ten sam obszar pamięci.
Tablicy jest przypisany, co do zasady, ciągły obszar pamięci o wielkości odpowiadającej iloczynowi maksymalnych rozmiarów obiektów które mogą być przechowywane w pojedynczej komórce i liczby komórek. Wielkość ta może być stała dla tablic, które mają raz deklarowaną wielkość (tablice statyczne) lub może zmieniać się wraz ze zmiana ilości elementów tablicy (tablice dynamiczne).
Zbiory są zazwyczaj rozproszone po całej pamięci i nie są reprezentowane przez spójny obszar. Istnieje wiele różnych rodzajów zbiorów i każdy z nich ma swoje charakterystyczne cechy dotyczące zajmowania pamięci.
Ważnym elementem jest też wartość "pusta", czyli NULL oznacza ona, że dana zmienna nie wskazuje na żaden obszar pamięci i nie zajmuje w niej miejsca.

Pojęcie wejścia i wyjścia


Co to jest komputer? Mówiąc formalnie jest to uproszczona Maszyna Turinga, w której uproszczenie polega na odstępstwie od zasady, że Maszyna ma nieskończoną pamięć. Skoro możemy zdefiniować komputer to pojawia się pytanie z jakich części składa się komputer? Teoretycznie każdy komputer musi składać się z następujących elementów:

  • Jednostki Arytmetyczno Logicznej - ALU
  • pamięci
  • modułu wejścia/wyjścia

ALU nie będziemy omawiać  w tym artykule. Pamięć już omówiliśmy, czas zatem na najbardziej widoczny element komputera czyli urządzeń wejścia/wyjścia (I/O). Element ten służy do komunikacji komputera ze światem zewnętrznym i może przybierać najróżniejsze postaci. Jeżeli popatrzymy na ten problem z punktu programisty to dość szybko stwierdzimy, że każdy język programowania udostępnia co najmniej dwie procedury pierwszą do przyjmowania i drugą do wyświetlani danych.
Wyświetlanie danych jest czynnością stosunkowo prostą gdyż ogranicza się do wywołania odpowiedniej procedury, która otrzymuje zestaw danych do wyświetlenia. Poza nielicznymi przypadkami nie ma potrzeby wykonywania dodatkowych operacji takich jak sprawdzenie czy cel dla danych istnieje.
Przyjmowanie danych jest procesem bardziej skomplikowanym ponieważ trzeba wskazać źródło tychże oraz sposób ich interpretacji. O ile pierwszą czynność można przerzucić na system operacyjny to druga wymaga od programisty wykonania kilku niezbędnych operacji. Wiąże się z nią też kilka zagrożeń takich jak wprowadzenie nieprawidłowych danych, danych w złym formacie lub w jakiś inny sposób nie zrozumiałych dla komputera. Najlepszą praktyką jest sprawdzanie czy dane, które zostały wprowadzone są poprawne, czyli nasz program je zrozumie.

Funkcja


Funkcja, w programowaniu obiektowym nazywana też metodą, ma znaczenie trochę inne do tego jakie znamy z matematyki. W matematyce funkcja wyraża powiązanie pomiędzy dwoma lub więcej wielkościami. W programowaniu funkcja służy do wydzielenia jakiegoś fragmentu instrukcji w celu uproszczenia głównego programu. Dodatkową zaletą jest pozbycie się konieczności wielokrotnego pisania tego samego kodu. Sama konstrukcja funkcji jest nieskomplikowana:
[FUNCTION] [MODYFIKATORY] [TYP_ZWRACANY] [NAZWA] ([parametry]){
  // instrukcje
  [RETURN [zmienna]]
}
[END]

Wywołanie polega na podaniu nazwy funkcji i opcjonalnej listy parametrów. Funkcja może, ale nie musi, zwracać jakaś wartość. Jeżeli funkcja nie zwraca żadnej wartości, a można to rozpoznać po słowie kluczowym void lub braku słowa return, to próba przypisania wyniku funkcji do jakiejś zmiennej może spowodować błąd kompilatora, albo przypisanie wartości NULL. W tym miejscu należy wspomnieć o podziale języków programowania ze względu na "siłę" kontroli typów. Niektóre języki np. C, Java, C++ mają "silną" kontrolę typów. Oznacza to, że jeżeli zmienna w definicji ma określony typ np. liczba naturalna (ang. integer) to nie można zmienić jej typu, ani przypisać jej wartości innego typu. Oczywiście niektóre przypisania są możliwe np. liczbie zmiennoprzecinkowej można przypisać wartość naturalną, ale czasami wymagane jest zadeklarowanie takiej operacji wprost przez rzutowanie. Wynika to z możliwości utraty danych. Druga grupę stanowią języki o "słabej" kontroli typów np. php, javascript. W tych językach zmienna posiada "najbardziej odpowiedni" typ, a o tym jaki on jest decyduje zazwyczaj intrepreter. Słaba kontrola typów jest charakterystyczną cechą języków skryptowych.

Obiekty, klasy, interfejsy


Ostatnim tematem jaki zostanie poruszony w tym artykule jest programowanie obiektowe. Obecnie najpopularniejsze języki programowania takie jak C++, Java, C# są językami obiektowymi. Cała "magia" programowania obiektowego opiera się o założenie, że operujemy na danych w postaci obiektów. Obiekty mają własności mogące być typami prostymi lub innymi obiektami oraz metody, czyli definicje pewnych czynności, które przez obiekt mogą być podejmowane. Ogólny opis obiektu to klasa. Zawiera ona zdefiniowaną listę pól oraz metod dla każdego obiektu. Sam obiekt jest już fizyczną reprezentacją klasy w pamięci. Z obiektami porozumiewamy się za pomocą interfejsów, które są ściśle zdefiniowaną listą pól i metod, które obiekt na pewno ma.

Podsumowanie


Ogólne omówienie najczęściej spotykanych konstrukcji w programowaniu jest dobrym materiałem na dość grubą książkę. Mam nadzieję, że ten artykuł choć trochę przybliżył podstawowe pojęcia związane z programowanie.  Zachęcam do dyskusji na forum.

7 komentarzy

Koziołek 2007-11-24 17:41

Sposób opisywania składni to rzecz czysto umowna. CamelCase, iPascalStyle itp są raczej sprawą do uzgodnienia, ale fajnie by było jak by osoby piszące w różnych językach na koniec podały próbki zasad formatowania składni. Dla osoby początkującej bardzo często ważniejszą rzeczą od funkcjonalności języka jest jego przejrzystość i łatwość czytania.

Wolverine 2007-11-24 08:08

Jeszcze nigdy nie widziałem, żeby ktoś klasę nazywał w stylu "malaDuzaDuza", w przeciwieństwie do zmiennych. Sam z resztą nie lubie underscorów i prawie nigdzie ich nie używam (prócz C i ASM). :)

A przykład kurcze bardzo dobry, każde szanowane środowisko ma intellisense więc wcale więcej się nie pisze. :)

SebaZ 2007-11-24 01:37

Za wielbłądowatość (neologizm - trudne słowo ;) ) zapewne.
Takiego stylu używa się raczej do nazywania klas, nie zmiennych.

Koziołek 2007-11-21 17:08

Oj chodziło o przykład. Swoją drogą za co by cię wywalili?

Deti 2007-11-21 16:57

"liczbaOsóbWPokoju" ... grr wywalili by mnie z roboty jakbym taką zmienną napisał ;)

Koziołek 2007-12-27 12:54

@bordeux, pisałem z pracy ostatnią część więc jeszcze wieczorem to poprawię troszkę. Jesli chodzi o kolorowanie to chodzi własnie o maksymalne odjęzykowienie fragmentów. Zostaje zatem code

bordeux 2007-12-27 10:50

Nie lepiej stosowac <delphi.> a nie . Moim zdaniem będzie się lepiej czytało. Już lepiej miec troche pokolorowane niz nic. (kropki dopisane aby mozna było znaczniki przeczytac)