Rozdział 1. Komputery i programy

> Dodaj do ulubionych

W tym rozdziale dowiecie się czym jest komputer. Zrozumiecie jak programy komunikują się z komputerami i przekazują im instrukcje. Nauczycie się też jak należy przystąpić do pisania programu, by wszystko „skończyło się szczęśliwie” dla was i waszych klientów. Ponadto przyjrzymy się programowaniu w ogóle i językowi C# w szczególności.

1.1. Komputery

Nim przejdziemy do rozważań nad programowaniem, zastanowimy się czym są komputery. To ważne, ponieważ wiedza ta stanowi kontekst dla wszystkich aspektów związanych z programowaniem.

1.1.1. Komputery — wprowadzenie

Pytanie: Dlaczego mucha bzyczy? Odpowiedź: Bo nie umie mówić!

Komputer można opisać jako podłączone do prądu pudło, które bzyczy. I choć z technicznego punktu widzenia to prawda, to takie wyjaśnienie może być mylące — zwłaszcza jeśli po przeczytaniu go ktoś chciałby zaprogramować lodówkę. Lepiej więc opisać komputer jako:

urządzenie przetwarzające informacje zgodnie z podanymi mu instrukcjami.

Taka ogólna definicja eliminuje lodówki i choć nie jest wyczerpująca, to wystarczy na nasze potrzeby. Instrukcje przekazywane komputerowi często nazywane są programem, natomiast korzystanie z komputera określane bywa programowaniem. Nie tym jednak zajmuje się większość ludzi używających komputerów. Większość nie pisze własnych programów, lecz korzysta z programów pisanych przez innych. Z tego powodu musimy dokonać rozróżnienia na użytkowników i programistów. Użytkownik ma do wykonania zadanie, które może mu ułatwić odpowiedni program komputerowy. Programista ma zaś masochistyczną potrzebę majstrowania przy wewnętrznych mechanizmach komputera. Jedna ze złotych zasad programowania brzmi: nie pisz własnego programu, jeśli już taki program istnieje, np. nasze nieposkromione pragnienie przetwarzania słów nie ma zaowocować nowym procesorem tekstu!

Często jednak będziemy chcieli wykorzystać komputery do zupełnie nowych zastosowań i, co więcej, znajdą się tacy, którzy będą chcieli nam za to zapłacić. Dlatego też nauczymy się zarówno używać komputerów, jak i je programować.

Nim jednak zajmiemy się fajowskim światem programowania, warto najpierw pochylić się nad terminologią komputerową.

1.1.2. Sprzęt i oprogramowanie

Kiedy kupujemy komputer, otrzymujemy coś więcej niż tylko bzyczące pudło. By był z niego jakiś pożytek, pudło to musi mieć wbudowaną umiejętność rozumienia i wykonywania prostych poleceń. Czas więc rozróżnić oprogramowanie systemu komputerowego od sprzętu.

Sprzęt to materialne składniki systemu. Krótko rzecz ujmując, jeśli możemy coś kopnąć, a po wylaniu na nie kubła wody przestanie działać, to mamy do czynienia ze sprzętem. Sprzęt to ta intrygująca, czająca się w kącie wieża lampek i przełączników, którą przynieśliśmy ze sklepu komputerowego.

Oprogramowanie natomiast sprawia, że nasz komputer zaczyna działać. Jeśli komputery mają duszę, to mieści się ona właśnie w oprogramowaniu. Oprogramowanie robi pożytek z fizycznych możliwości sprzętu, dzięki którym możliwe jest uruchamianie programów. Nie ma ono swojej materialnej postaci i stosunkowo łatwo można wprowadzić w nim zmiany. To głos komputera w Star Treku.

Windows 10 to przykład systemu operacyjnego. Stanowi on platformę, na której mogą być wykonywane programy komputerowe.

Wszystkie komputery sprzedawane są z jakimś oprogramowaniem. Bez niego byłyby tylko bardzo drogimi, awangardowymi grzejnikami. Dostarczane z komputerem oprogramowanie często nazywane jest systemem operacyjnym. To dzięki niemu z urządzenia tego da się korzystać. System operacyjny kontroluje przechowywane na komputerze informacje i udostępnia wiele poleceń, które można wydawać komputerowi. System umożliwia także wykonywanie programów — tych, które napisaliśmy sami i tych autorstwa innych programistów. Będziemy musieli nauczyć się komunikować z systemem operacyjnym, by móc tworzyć programy C# i je uruchamiać.

1.1.3. Dane a informacje

Słowa dane i informacje są przez większość ludzi używane zamiennie i ze sobą utożsamiane. Moim zdaniem słowa te oznaczają jednak dwie różne rzeczy:

Dane to zbiór zerojedynkowych ustawień, które są przechowywane i przetwarzane przez komputery.

Informacje są natomiast dokonywaną przez ludzi interpretacją danych. Precyzyjnie rzecz ujmując, komputery przetwarzają dane, natomiast ludzie pracują na informacjach. Przykładowo mamy komputer, który przechowuje w pamięci taki oto ciąg bitów:

11111111 11111111 11111111 00000000

Można mu przypisać różne znaczenia, np.:

„jesteś zadłużony w banku na 256 złotych”

albo

„znajdujesz się 256 metrów pod powierzchnią ziemi”

albo

„osiem z trzydziestu dwóch przełączników światła jest wyłączonych”.

O informacjach zaczynamy mówić zwykle wtedy, gdy człowiek odczytuje dane wyjściowe z maszyny. Dlaczego więc jestem taki pedantyczny? Ponieważ należy pamiętać, że komputer „nie wie” co tak naprawdę oznaczają przetwarzane przez niego dane. Dla komputera dane to tylko układ bitów — znaczenie nadaje mu dopiero człowiek. Warto o tym pamiętać, gdy dostaniecie wyciąg z banku z informacją, że na waszym koncie znajduje się 8 388 608 złotych!

Przetwarzanie danych

Komputery zajmują się przetwarzaniem danych. Otrzymują informacje, robią coś z nimi, po czym generują kolejną porcję informacji. Program przekazuje komputerowi co ten ma zrobić z przychodzącymi informacjami. Komputer operuje na danych w ten sam sposób, co maszynka do mielenia na mięsie — coś wchodzi z jednej strony, zostaje przetworzone i wychodzi z drugiej:

dane komputer dane rysunek
Z tego powodu komputery mogą łatwo namnażać błędy — i zawsze jest na co zrzucić winę…

Program nie wie, czym są przetwarzane dane, tak jak maszynka nie wie czym jest mięso. Możemy do niej wrzucić worek gwoździ, a i tak spróbuje je przemielić. Tak samo możemy dostarczyć komputerowi nieprawidłowych danych, a ten spróbuje zrobić z nimi coś równie bezsensownego. Tylko ludzie mogą nadać danym prawdziwe znaczenie (patrz wyżej). Z punktu widzenia komputera dane to tylko coś, na czym trzeba wykonać jakieś operacje.

Program komputerowy to tylko sekwencja instrukcji, które mówią komputerowi, co należy zrobić z danymi wejściowymi i jaką postać mają mieć dane wyjściowe.

Przetwarzanie danych przez komputer nie ogranicza się, jak mogłoby się wydawać, jedynie do odczytywania i zapisywania liczb. Ma ono o wiele szersze zastosowanie. Oto kilka typowych przykładów:

Zegarek elektroniczny: znajdujący się w zegarku mikrokomputer rejestruje drgania kryształu i żądania przekazywane za pośrednictwem guzików, przetwarza otrzymane dane, a następnie wyświetla godzinę.

Samochód: mikrokomputer silnika pobiera z czujników informacje na temat prędkości obrotowej silnika, prędkości jazdy, zawartości tlenu w powietrzu, położenia pedału gazu itd. Ponadto kontroluje napięcie, które przekłada się na ustawienia gaźnika, moment wytworzenia iskry itp., tym samym optymalizując wydajność silnika.

Odtwarzacz płyt CD: komputer odbiera sygnał z płyty i konwertuje go na dźwięk, który chcemy usłyszeć. Jednocześnie dba o to, by głowica lasera była precyzyjnie umiejscowiona, a także monitoruje wszystkie przyciski — na wypadek gdybyśmy chcieli wybrać inną ścieżkę.

Konsola gier: komputer pobiera z kontrolerów instrukcje i na ich podstawie generuje dla gracza odpowiedni świat gry.

Zauważcie, że w niektórych z powyższych przykładów przetwarzanie danych wykorzystywane jest jedynie po to, by za pomocą technologii ulepszyć istniejące już urządzenia. Przetwarzanie danych stanowi jednak podstawę działania odtwarzaczy czy konsoli.

W większości względnie złożonych urządzeń komponenty odpowiedzialne za przetwarzanie danych pełnią funkcję dodatkową, np. umożliwiają optymalizację wydajności, ale są też urządzenia, których podstawowe działanie jest w pełni uzależnione od tych komponentów. To w takim świecie poruszają się programiści. Ważne, by postrzegać przetwarzanie danych jako coś więcej niż sposób na zestawianie listy płac, odczytywanie liczb i zwracanie wyników. To tradycyjne zastosowania komputerów.

Warto zauważyć, że w związku z tą tendencją „gra toczy się o większą stawkę”, co oznacza, że błędnie działające oprogramowanie może za sobą pociągać dalece idące konsekwencje.

Inżynierowie oprogramowania muszą spędzać wiele czasu na dopasowywaniu do urządzeń komponentów przetwarzania danych, które umożliwią funkcjonowanie sprzętu. Nie będziemy używać przycisków, by coś uruchomić, lecz by przekazać polecenie uruchomienia komputerowi. Układy wbudowane sprawią, że wszyscy staną się użytkownikami komputerów. My — programiści — będziemy musieli zadbać o to, by korzystali z nich nieświadomie.

Należy pamiętać również o tym, że pozornie nieszkodliwe programy mogą stanowić zagrożenie dla życia. Przykładowo lekarz może obliczać dawki leków dla pacjentów przy pomocy arkusza kalkulacyjnego. Błąd programu mógłby skutkować chorobą, a nawet śmiercią (nie sądzę, by lekarze stosowali taką praktykę, ale nigdy nic nie wiadomo…).

Złota myśl programisty: działanie komputera opiera się na sprzęcie

Należy pamiętać, że programy są w rzeczywistości wykonywane przez sprzęt, który ma ograniczenia fizyczne. Musimy mieć zatem pewność, że pisany przez nas kod będzie odpowiedni dla docelowego urządzenia i zostanie przez nie sprawnie wykonany. Przy dzisiejszej mocy i możliwościach komputerów nie jest to tak problematyczne jak kiedyś, lecz wciąż trzeba mieć tego rodzaju kwestie na uwadze. Będę o nich wspominał w odpowiednim momencie.

1.2. Programy i programowanie

Mówię ludziom, że jestem „inżynierem oprogramowania”.

Programowanie to czarna magia. To jedna z tych rzeczy, do której niechętnie się przyznajemy, bo zajmujemy się nią cichaczem po nocach. Jeśli powiecie komuś, że programujecie komputery, wasz rozmówca zareaguje na jeden z poniższych sposobów:

  1. Spojrzy na was pustym wzrokiem.
  2. Powie „to ciekawe”, po czym zacznie wam szczegółowo opowiadać o remoncie swojego mieszkania.
  3. Poprosi was o rozwiązanie swoich wszelkich problemów z komputerem, również tych potencjalnych.
  4. Spojrzy na was sugerując, że najwyraźniej nie jesteście zbyt dobrzy w tym co robicie, bo wszyscy programiści jeżdżą Ferrari i mają górę kasy.

Zdaniem większości ludzi programowanie polega na zarabianiu bajońskich sum na robieniu czegoś, czego nikt nie rozumie.

Ja natomiast definiuję programowanie jako opracowywanie rozwiązań problemów i wyrażanie ich w postaci, która jest dla komputera zrozumiała i przez niego wykonalna.

Z definicji tej wynikają dwie rzeczy:

  • programista musi sam wiedzieć jak rozwiązać dany problem nim napisze program, który zrobi to za niego;
  • komputer musi zrozumieć to, co programista chce mu przekazać.

1.2.1. Kim jest programista

I pomyśleć ile zarabiają hydraulicy…

Moim zdaniem programista przypomina nieco hydraulika! Hydraulik przychodzi do klienta z zestawem narzędzi i części zapasowych. Ogląda usterkę po czym, mrucząc coś pod nosem, otwiera skrzynkę z różnymi narzędziami i częściami, a następnie dopasowuje je do siebie, by rozwiązać zaistniały problem. Programowanie wygląda tak samo. Otrzymujemy do rozwiązania problem. Mamy do dyspozycji zestaw sztuczek — w naszym przypadku jest to język programowania. Przyglądamy się przez chwilę problemowi, po czym staramy się wypracować jego rozwiązanie. W tym celu łączymy ze sobą elementy języka. Sztuka programowania polega na odpowiednim doborze dostępnych sztuczek, tak by rozwiązać poszczególne części problemu.

Od problemu do programu

W programowaniu nie chodzi o obliczenia matematyczne, tu liczy się organizacja i struktura.

Rozbijanie problemu na zbiór instrukcji, które przekazujemy komputerowi sprawia, że programowanie jest ciekawe. Jednocześnie, niestety, jest to jego najtrudniejszy aspekt. Jeśli sądzicie, że nauka programowania to kwestia wyuczenia się języka, to jesteście w dużym błędzie. I jeśli waszym zdaniem programowanie sprowadza się do napisania programu rozwiązującego jakiś problem, to również się mylicie!

Pisząc program, należy rozważyć wiele rzeczy, z których nie wszystkie będą związane z samym problemem. Załóżmy, że piszemy programy dla klienta. Klient ma problem i chciałby, żebyśmy napisali program, który go rozwiąże. Musimy przyjąć, że klient wie o komputerach mniej niż my sami!

Na początku nie będziemy nawet poruszać kwestii języka programowania czy rodzaju komputera. Musimy się tylko upewnić, że wiemy, czego klient od nas oczekuje.

Dobre rozwiązanie złego problemu

W praktyce zaskakująco często zdarza się, że programista wypracowuje świetne rozwiązanie nieistniejącego problemu. Wiele projektów zakończyło się fiaskiem, ponieważ programiści rozwiązali nie ten problem, co było trzeba. Zwyczajnie nie ustalili czego od nich oczekiwano i opracowali coś, co w ich mniemaniu było oczekiwane. Klienci natomiast wyszli z założenia, że skoro programiści nie zadają im już więcej pytań, to pracują nad właściwym produktem. Dopiero moment odbioru projektu uświadomił im gorzką prawdę. Dlatego też tak ważne jest, by programista wstrzymał się z pracą dopóki nie dowie się, czego tak naprawdę oczekuje klient.

Najgorsza rzecz jaką możecie powiedzieć klientowi to „dam radę to zrobić”. Zamiast tego należy zadać sobie pytanie „Czy to na pewno tego chce klient?”.

To forma samodyscypliny. Programiści szczycą się tym, że potrafią wypracowywać rozwiązania, zatem gdy tylko otrzymają problem, starają się go rozgryźć. To odruchowe. Należy jednak zastanowić się, czy na pewno rozumiemy w czym tkwi problem. Nim przejdziemy do jego rozwiązania, musimy upewnić się, że został on wyczerpująco zdefiniowany i że co do tej definicji zgadzamy się zarówno my, jak i klient.

W praktyce taka definicja problemu bywa nazywana specyfikacją funkcjonalną. Dokument ten mówi nam dokładnie czego oczekuje klient. Podpisuje go programista i klient — jeśli więc dostarczymy system, który działa zgodnie ze specyfikacją, to klient musi nam zapłacić.

Gdy już mamy sporządzoną specyfikację, możemy zająć się rozwiązywaniem problemu. Mogłoby się wydawać, że takie postępowanie nie jest konieczne, jeśli piszemy program na własne potrzeby — nie ma przecież klienta, którego wymagania musimy spełniać. To jednak błędne rozumowanie. Pisanie specyfikacji zmusza nas do myślenia o problemie w bardzo szczegółowym wymiarze. Ponadto musimy zastanowić się czego nasz system ma nie robić i od samego początku wiemy, jakie są oczekiwania klienta.

Złota myśl programisty: specyfikacja jest nieodzowna

Napisałem wiele programów za pieniądze i nigdy nie napisałbym żadnego bez rozpoczęcia pracy od przygotowania porządnej specyfikacji. Dbam o to nawet gdy wykonuję zlecenie dla znajomego (a może i w szczególności wtedy).

Dzisiejsze techniki rozwoju oprogramowania sprawiają, że klient znajduje się w samym centrum procesu projektowania i bierze w nim udział. Stosowane obecnie schematy pracy utrudniają opracowanie wyczerpującej specyfikacji na początku projektu (i czynią ją mniej użyteczną). Programista nie wie zbyt wiele o działalności klienta, a klient nie jest świadomy możliwości i ograniczeń wykorzystywanej przez programistę technologii. Mając to na uwadze, warto przygotować kilka propozycji rozwiązania problemu i omówić je z klientem przed przejściem do kolejnego etapu pracy. To tak zwany model prototypowy tworzenia oprogramowania.

1.2.2. Prosty problem

Wyobraźmy sobie taką sytuację: siedzimy sobie w naszym ulubionym barze, rozmyślając o niebieskich migdałach, gdy nagle nasz błogostan przerywa kumpel, który zajmuje się sprzedawaniem szyb. Wie, że trudnimy się programowaniem i chciałby uzyskać od nas pomoc w rozwiązaniu swojego problemu.

Kolega od niedawna produkuje własne okna z podwójnym szkleniem i potrzebuje programu, który obliczałby koszt materiałów. Chce, by do programu dało się wprowadzać wymiary okna i obliczać koszt jego wykonania, zależny od wymaganej ilości drewna i szkła.

Myślimy sobie „niezła fucha” i po ustaleniu ceny zaczynamy pracę. Naszym pierwszym zadaniem jest ustalenie, czego tak naprawdę oczekuje nasz klient…

Określenie problemu

Przystępując do napisania specyfikacji systemu należy wziąć pod uwagę trzy istotne rzeczy:

  • jakie informacje będą trafiać do systemu,
  • co system będzie zwracał,
  • co system będzie robił z otrzymanymi informacjami.

Wszystko to można przedstawić na wiele sposobów przy pomocy diagramów, jednak póki co skupmy się na pisemnym opisie każdego z etapów.

Informacje wejściowe

W przypadku okien możemy określić, że podawanymi informacjami będą:

  • szerokość okna,
  • wysokość okna.

Informacje wyjściowe

Nasz klient chce otrzymać następujące informacje:

  • powierzchnię szyby potrzebną do wyprodukowania okna;
  • długość drewna potrzebnego do zbudowania ramy.

Poniższy diagram pokazuje, czego potrzebujemy:

Szerokość wysokość okna schemat
Powierzchnia szyby to szerokość pomnożona przez wysokość. Do wykonania ramy potrzebne będą dwa kawałki drewna o długości równej szerokości okna i dwa odpowiadające jego wysokości

Złota myśl programisty: metadane są ważne

Informacje o informacjach nazywamy metadanymi. W kontekście programistycznym słowo „meta” oznacza, że robimy krok w tył, by zobaczyć problem w szerszej perspektywie. W przypadku programu dla naszego znajomego metadane powiedzą nam więcej na temat wykorzystywanych i wyliczanych wartości — a konkretnie na temat jednostek, w jakich mają być wyrażane informacje i zakresu wartości, jakie mogą przyjąć. Reprezentowana w programie ilość musi być zawsze opatrzona metadanymi zawierającymi przynajmniej te informacje.

Co tak naprawdę robi program

Nasz program może wyliczyć dwie wartości z poniższych wzorów:

powierzchnia szyby = szerokość okna * wysokość okna 
długość drewna = (szerokość okna + wysokość okna) * 2

Więcej szczegółów

Wiemy już dosyć dobrze, co ma robić nasz program. Jako że jednak jesteśmy rozsądni i przezorni, ta wiedza nam nie wystarcza. Zastanawiamy się, jak nasz program będzie oceniał, czy podane mu informacje są poprawne.

W nasze rozważania musimy zaangażować także klienta — musi on zrozumieć, że jeśli program otrzyma informacje z określonego zakresu, to uzna je za prawidłowe i wykona odpowiednie operacje.

W przypadku naszego programu moglibyśmy zatem bardziej szczegółowo zdefiniować informacje wejściowe jako:

  • szerokość okna, podawaną w metrach, stanowiącą wartość z przedziału między 0,5 a 3,5 metra włącznie;
  • wysokość okna, podawaną w metrach, stanowiącą wartość z przedziału między 0,5 metra a 2 metry włącznie.

Warto zauważyć, że uzupełniliśmy nasz opis o jednostki. To bardzo istotne — nasz klient może zaopatrywać się u dostawcy, który sprzedaje drewno na stopy i w takim przypadku program powinien wyświetlać odpowiedni komentarz:

  • Powierzchnia szyby potrzebna do wyprodukowania okna, w metrach kwadratowych. Musimy też pamiętać o tym, że nasz znajomy sprzedaje okna z podwójnym szkleniem, zatem będzie potrzebował dwóch szyb.
  • Długość drewna potrzebna do wykonania ramy, podawana w stopach według przelicznika: 1 metr = 3,25 stopy.

Specyfikacja musi być zrozumiała zarówno dla programisty, jak i dla klienta!

Gdy już ujmiemy to wszystko w sposób zrozumiały dla nas i dla klienta, obie strony muszą podpisać ukończoną specyfikację i można przystąpić do pracy.

Prezentacja programu

Na tym etapie programista napisałby test, który udowodniłby, że program działa. Działanie testu można by opisać klientowi następująco:

„Jeśli podam programowi, że wysokość wynosi 2 metry, a szerokość 1 metr, to program powinien wyliczyć, że potrzebuję 4 metrów kwadratowych szyby i 19,5 metra drewna”.

Procedura testowania prawdziwego projektu powinna uwzględniać wszelkie możliwe stany programu, w tym — co niezwykle ważne — sytuacje powodujące błędy. W przypadku dużego systemu osoba pisząca program może opracować kontroler testowania, który umożliwi przeprowadzenie testów. Klient i dostawca powinni wspólnie ustalić, ile należy przeprowadzić testów i jakiego mają być rodzaju. Następnie należy podpisać dokument potwierdzający te ustalenia.

Testowanie stanowi bardzo istotną część rozwoju oprogramowania. Istnieje nawet taka technika, zgodnie z którą testy pisze się jeszcze przed napisaniem docelowego programu. W gruncie rzeczy jest to dobre rozwiązanie, któremu przyjrzymy się bliżej w dalszej części książki. Jeśli chodzi o pisanie kodu, to kod testów może być równie obszerny co kod właściwego programu. Warto o tym pamiętać, gdy szacujemy ile pracy będzie od nas wymagał dany projekt.

Dzień wypłaty

Jeszcze lepiej uzgodnić zapłatę w ratach, dzięki czemu będziemy otrzymywać pieniądze w trakcie prac nad systemem.

Na tym etapie dostawca oprogramowania wie, że jeśli utworzony system przejdzie pomyślnie wszystkie testy, to klient będzie musiał mu zapłacić za wykonaną pracę! Faza projektowania i testowania została zakończona, zatem nie istnieją żadne niejasności, na podstawie których klient mógłby zażądać poprawek — choć oczywiście nic nie jest niemożliwe!

Dobra wiadomość dla programisty jest taka, że jeśli klient zażąda zmian, to można traktować je jako dodatkową pracę, za którą należy się zapłata.

Wkład klienta

Warto zauważyć, że w przypadku „porządnie” prowadzonego projektu klient oczekuje, że będziemy z nim konsultować takie rzeczy jak sposób interakcji programu z użytkownikiem, a nawet kolor liter! Pamiętajcie, że jeden z największych błędów jakie możemy popełnić, to założyć, że znamy oczekiwania klienta. Klient na pewno będzie mieć własne zdanie na temat interakcji z użytkownikiem — składa się na nią np. zachowanie programu w przypadku wystąpienia błędu czy sposób prezentowania informacji. Optymalnie opis interakcji powinien zostać ujęty w specyfikacji programu i obejmować układ ekranów, a także informację dotyczącą klawiszy, jakie należy kolejno wciskać. Dosyć często korzysta się także z prototypów, które pomagają wyobrazić sobie wygląd i działanie programu.

Fakt: jeśli chcielibyście opracowywać specyfikację na bieżąco podczas prac nad projektem, to albo nie wykonacie zlecenia, albo będzie ono od was wymagało pięciokrotnie większego nakładu pracy!

Jeśli odnosicie wrażenie, że angażując klienta ułatwiacie sobie pracę, to dobrze się wam wydaje! Klient może wprawdzie wyjść z założenia, że po otrzymaniu opisu problemu zamkniemy się w swoim pokoju i sami wypracujemy idealne rozwiązanie. Tak się jednak nie stanie. Nasz program będzie spełniał oczekiwania w około 60%. Klient powie nam, co mu się podoba, a co wymaga poprawki. Mrucząc pod nosem, znów udamy się do naszego pokoju i opracujemy kolejny system wymagający akceptacji. I znowu, jak mówi prawo Roba, uda nam się poprawić 60% z dotychczasowych 40% błędnych rozwiązań. Zaakceptujemy ostatnie zmiany i ponownie chwycimy za klawiaturę…

Z perspektywy klienta to świetny układ, w którym programista przypomina dobrego krawca, któremu po licznych poprawkach udaje się stworzyć kreację leżącą jak ulał. Klient tylko ogląda produkt, sugeruje zmiany i czeka na kolejną wersję, w której będzie mógł znaleźć kolejne rzeczy do poprawy. Złości się nieco, kiedy zamówienie nie zostaje oddane na czas, lecz w ramach pocieszenia zawsze może nas pozwać.

W tym miejscu zataczamy koło — jak już wspomniałem, dobrym rozwiązaniem jest stworzenie prototypu systemu, jeśli wstępna specyfikacja nie jest wyczerpująca. Jeśli jednak zamierzacie korzystać z prototypów, to warto to zaplanować już na samym początku zamiast dokładać sobie pracy później, gdy okaże się, że nie zrozumieliśmy na czym polega problem.

Fakt: główną przyczyną powstawania wadliwych implementacji jest nieodpowiednia specyfikacja!

Jeśli jednak postawimy na żelazną specyfikację, zmuszając tym samym klienta do zastanowienia się, do czego dokładnie ma służyć opracowywany system i jak ma działać, to tym lepiej. Klient może wprawdzie powiedzieć: „nie znam się na komputerach, to pan/i jest tutaj ekspertem”. To jednak żadna wymówka. Należy przedstawić klientowi korzyści, jakie daje opracowanie produktu nie wymagającego poprawek już za pierwszym podejściem. Jeśli natomiast dalej będzie stawiać opór, to trzeba to załatwić siłą!

Raz jeszcze podkreślę: wszystkie powyższe zasady należy stosować nawet jeśli programujemy sami dla siebie. Programista jest swoim najgorszym klientem!

Możecie odnosić wrażenie, że niepotrzebnie się tak nad tym rozwodzę. Biorąc pod uwagę jak proste są systemy, którymi będziemy się zajmować podczas naszej nauki programowania, wymienione techniki mogą wydawać się nazbyt skomplikowane. Nie macie jednak racji. Jedną z istotnych zalet stosowania specyfikacji jest to, że pisząc taki dokument, jednocześnie otrzymujemy już większość programu — i to często przy pomocy klienta. Przystępując teraz do pracy nad naszym kalkulatorem materiałów do produkcji okien wiemy już, że musimy:

wczytać szerokość 
zweryfikować wprowadzoną wartość 
wczytać wysokość 
zweryfikować wprowadzoną wartość 
pomnożyć szerokość przez wysokość przez 2 i zwrócić wynik 
wykonać obliczenie wg wzoru (szerokość + wysokość) * 2 * 3,25 i zwrócić wynik

Część programistyczna naszego zadania sprowadza się teraz do wyrażenia powyższego opisu w języku zrozumiałym przez komputer…

Złota myśl programisty: dobrzy programiści potrafią się dobrze komunikować

Umiejętność rozmowy z klientem i odkrywania jego potrzeb to nic innego jak sztuka. Musicie ją opanować, jeśli chcecie być programistami z prawdziwego zdarzenia. Trzeba zacząć od porzucenia myślenia typu „To ja piszę program dla klienta” i zastąpić je podejściem „Projektuję rozwiązanie problemu wspólnie z klientem”. Nie pracujemy dla klientów, tylko z nimi współpracujemy. To bardzo ważne, zwłaszcza kiedy może nam przyjść targować się z klientem o cenę bądź zakres funkcjonalności programu.

1.3. Języki programowania

Gdy wiemy już, co ma robić nasz program (ze specyfikacji) i jak sprawdzić czy działa prawidłowo (za pomocą testów), czas wyrazić go w postaci, która może zostać przetworzona przez komputer.

Być może zadajecie sobie pytanie „Po co nam języki programowania, nie moglibyśmy używać np. angielskiego?”. Odpowiedzi są dwie:

  1. Komputery są zbyt głupie, by zrozumieć angielski.
  2. Angielski byłby nędznym językiem programowania.

Nie próbuję tu zasugerować, że ameby sprawdziłyby się w roli programistów!

Zacznijmy od kwestii numer 1. Na tę chwilę nie możemy uczynić komputerów mądrzejszymi niż są. Inteligencja komputerów wywodzi się z dostarczanego im oprogramowania. Programy mają jednak swoje ograniczenia, zarówno jeśli chodzi o rozmiar, jak i szybkość działania. Obecnie, przy zastosowaniu najnowszego oprogramowania i sprzętu, możemy sprawić, by komputer był równie mądry co ameba. Ameby nie mówią zbyt dobrze po angielsku. Nie da się więc stworzyć komputera, który rozumiałby język angielski. Najlepsze co możemy zrobić, to sprawić, by komputer zrozumiał bardzo ograniczony język, którym będziemy się z nim komunikować.

Samochód wyprzedził tramwaj. Czyli kto wyprzedzał?

Jeśli zaś chodzi o punkt drugi: każdy język naturalny jest pełen dwuznaczności. Bardzo trudno wyrazić się jednoznacznie. Jeśli mi nie wierzycie, zapytajcie byle prawnika!

Języki programowania rozwiązują oba wymienione problemy. Są wystarczająco nieskomplikowane, by zrozumiał je komputer, a także redukują dwuznaczności.

Złota myśl programisty: język nie gra aż takiej roli

Istnieje wiele języków programowania i na przestrzeni swojej kariery będziecie musieli poznać więcej niż jeden z nich. C# świetnie sprawdza się dla początkujących, jednak nie myślcie, że będzie to wasz jedyny język po wsze czasy.

1.4. C# (C sharp)

Istnieją dosłownie setki języków programowania. Musicie znać co najmniej 3!

Nauczymy się języka o nazwie C# (wymawianego z języka angielskiego jako C sharp). Jeśli nazwalibyście go C hash, to od razu wyszlibyście na ignorantów! C# to bardzo elastyczny język programowania, który ma wielkie możliwości i ciekawą historię. Jest on dziełem firmy Microsoft, a jego powstanie było motywowane różnymi przesłankami: technicznymi, politycznymi, marketingowymi.

C# w dużym stopniu przypomina C++ i Javę, ponieważ bazuje na elementach funkcjonalności zawartych w tych językach (a nawet je ulepsza). Początki Javy i C++ sięgają bardzo niebezpiecznego acz zabawnego języka o nazwie C, stworzonego na początku lat 70. ubiegłego wieku. C zyskał sławę jako język, w którym napisany został system operacyjny UNIX i do tego też celu został opracowany.

1.4.1. Niebezpieczny język C

Nazwałem język C niebezpiecznym. Co miałem na myśli? Otóż wyobraźcie sobie piłę łańcuchową. Jeśli ja, Rob Miles, zechcę użyć piły, to sobie ją wypożyczę. Jako niedoświadczony użytkownik tego sprzętu zakładam, że ma on odpowiednie zabezpieczenia, takie jak osłony czy funkcję automatycznego wyłączania. Dzięki nim będę mógł bezpieczniej korzystać z piły, choć pewnie ograniczy to jej możliwości — przez wszystkie te zabezpieczenia może nie dać się ciąć pewnych gatunków drzew. Gdybym był prawdziwym drwalem, to poszedłbym do sklepu i kupił sobie profesjonalną piłę łańcuchową, która choć pozbawiona wszelkich zabezpieczeń, poradziłaby sobie z praktycznie każdym drzewem. Jeśli popełniłbym błąd, korzystając z profesjonalnego narzędzia, to mógłbym sobie np. odciąć nogę. Amatorski sprzęt zapobiegłby takiemu wypadkowi.

Przekładając to na programowanie, chodzi o to, że w języku C brakuje zabezpieczeń, które są zawarte w innych językach programowania. Dzięki temu język ten jest o wiele elastyczniejszy.

Jeśli jednak zechcę napisać coś głupiego, to C mi w tym nie przeszkodzi. Istnieje więc większe ryzyko, że zawieszę komputer programem napisanym w C niż gdybym korzystał z innego języka.

Złota myśl programisty: komputer zawsze jest głupi

Uważam, że zawsze należy wychodzić z założenia, że komputer nie wybaczy nam żadnych błędów, a każda nasza głupota niechybnie skończy się katastrofą! Świetnie mi to poprawia koncentrację.

1.4.2. Bezpieczny C#

Twórcy języka C# postarali się odpowiednio wyważyć ryzyko związane z jego używaniem. Program C# może zawierać kod zarządzany i niezarządzany. Zarządzana porcja kodu podlega kontroli systemu, który go uruchamia, dzięki czemu trudno jest zawiesić komputer, wykonując ten rodzaj kodu (choć pewnie nie jest to niemożliwe). Kontrola ta ma jednak swoją cenę i powoduje wolniejsze działanie programów.

By osiągnąć maksymalną wydajność i umożliwić bezpośredni dostęp do części systemu komputerowego, można oznaczyć programy jako niezarządzane. Program niezarządzany jest szybszy, jednak gdy się wysypie, to może zawiesić cały system.

Przełączenie się na kod niezarządzany to jak zdjęcie przeszkadzającej nam osłony z łańcucha piły.

Język C# świetnie nadaje się na początek nauki, ponieważ dzięki kodowi zarządzanemu będzie wam łatwiej zrozumieć dlaczego wasze programy nie działają prawidłowo.

1.4.3. C# i obiekty

C# jest językiem obiektowym. Obiekty to mechanizmy strukturalne, które umożliwiają rozbicie programu na sensowne części, z których każda stanowi fragment głównego systemu. Techniki obiektowe ułatwiają projektowanie, testowanie i rozbudowywanie dużych projektów, jednocześnie umożliwiając tworzenie bardzo stabilnych i w dużej mierze niezawodnych programów.

Osobiście jestem wielkim fanem programowania obiektowego, lecz na razie nie będę go objaśniać. Nie dlatego, że mam niewystarczającą wiedzę na ten temat (mówię szczerze) — uważam jednak, że nim zaczniemy wykorzystywać obiekty w programach, to musimy pochylić się nad kilkoma podstawowymi zagadnieniami z dziedziny programowania.

Obiekty są związane zarówno z projektowaniem, jak i z programowaniem, więc zanim weźmiemy się za projektowanie dużych systemów, musimy nauczyć się programować.

1.4.4. Uruchamianie programów C#

Programy pisze się tak naprawdę przy użyciu swego rodzaju edytora tekstu, który może być częścią programu służącego do kompilacji i uruchamiania kodu.

C# to kompilowany język programowania. Komputer nie jest w stanie zrozumieć języka bezpośrednio, dlatego program zwany kompilatorem musi przetworzyć tekst napisany w C# na niskopoziomowe instrukcje, które są o wiele mniej skomplikowane. Instrukcje te są następnie przetwarzane do postaci poleceń zrozumiałych przez sprzęt wykonujący nasz program.

Tym aspektom działania programów C# przyjrzymy się jednak nieco później. Na razie musicie tylko pamiętać, że nim uruchomicie swój świetny program napisany w C#, to musi on zostać przetworzony przez kompilator.

Kompilator to bardzo duży program, który wie jak rozpoznać czy napisany przez nas kod jest prawidłowy. Na początek kompilator szuka błędów w sposobie użycia języka. Kompilacja powiedzie się tylko, jeśli nie zostaną wykryte żadne błędy.

Kompilator wyświetla też ostrzeżenia, jeśli uzna, że choć kod jest poprawny, to mogliśmy się pomylić w pisaniu programu. Ostrzeżenie możemy więc zobaczyć np. gdy utworzyliśmy w kodzie coś, czego nie używamy. Kompilator zwróci na to naszą uwagę, ponieważ mogliśmy zapomnieć dodać jakąś część programu.

Język C# wyposażony jest w całą masę innych rzeczy (że tak się fachowo wyrażę), które umożliwiają programom C# np. odczytywanie tekstu wprowadzonego z klawiatury, wyświetlanie napisów, konfigurację połączeń sieciowych itd. Możecie korzystać z tych dodatków w swoich programach C#, lecz musicie się do nich bezpośrednio odwołać. Wówczas zostaną one automatycznie wczytane przy uruchomieniu programu. W dalszej części książki zobaczycie jak rozbić własny program na kilka różnych części (by np. mogło nad nimi pracować kilku programistów).

1.4.5. Tworzenie programów C#

Firma Microsoft opracowała narzędzie o nazwie Visual Studio, które stanowi świetne środowisko do pisania programów. Składa się na nie kompilator wraz z wbudowanym edytorem, a także debuger. Program dostępny jest w wielu wersjach, zawierających różne zestawy funkcji. Na dobry początek świetnie sprawdza się wersja darmowa — Visual Studio Express. Darmowe jest także środowisko Microsoft .NET Framework. Platforma ta dostarcza narzędzi wiersza poleceń, czyli instrukcji wpisywanych w konsoli, którymi można posłużyć się do skompilowania i uruchomienia programów C#. Sposób tworzenia i uruchamiania programów zależy od was.

Nie będę się tu rozwodził na temat tego, jak pobrać i zainstalować platformę .NET. Informacje te znajdziecie w innych dokumentach. Zakładam, że będziecie pracować na komputerze z edytorem tekstu (najczęściej jest to Notatnik) i zainstalowaną platformą .NET.

Ludzki komputer

Pracę nad programem optymalnie jest zacząć od kartki. Moim zdaniem programy najlepiej pisze się wtedy, gdy nie siedzimy przed komputerem, dlatego najlepiej rozpisać (choćby szkicowo) nasze rozwiązanie na papierze, z dala od komputera. Gdy siedzimy przy klawiaturze, mamy dużą pokusę, by od razu zacząć wciskać klawisze — a nuż uda się nam napisać coś użytecznego. Nie jest to dobra metoda. Z dużym prawdopodobieństwem stworzymy program, który prawie działa, po czym będziemy przy nim dłubać kilka godzin, by zaczął funkcjonować jak należy.

Gdy zaś usiądziemy z ołówkiem w dłoni i zastanowimy się nad rozwiązaniem jakie chcemy wprowadzić, to opracowanie działającego systemu zajmie nam pewnie o połowę mniej czasu.

Złota myśl programisty: świetni programiści mniej debugują

Nie robią na mnie wrażenia programiści, którzy całe dnie poświęcają walce z dużymi programami i dzięki sztuczkom stawiają je na nogi. Jestem pod wrażeniem, jeśli programista potrafi napisać działający program za pierwszym podejściem!

1.4.6. Z czego składa się program C#

Gdyby mama chciała nam powiedzieć jak zrobić nasze ulubione ciasto, to na kartce napisałaby nam przepis. Zawierałby on listę składników oraz czynności, jakie należy kolejno wykonać z ich użyciem.

Program również można traktować jak przepis, lecz przeznaczony nie dla kucharza, a dla komputera. Składniki stanowią w nim wartości (nazywane zmiennymi), których chcemy użyć w trakcie programowania. Sam program składa się zaś z szeregu działań (tak zwanych instrukcji), które wykonuje komputer. Programu nie pisze się na kartce, lecz zapisuje się go w pliku komputerowym, często nazywanym plikiem źródłowym.

Na nim właśnie operuje kompilator. Plik źródłowy zawiera trzy elementy:

  • instrukcje dla kompilatora,
  • informacje dotyczące struktur zawierających dane, które będą przechowywane i przetwarzane,
  • instrukcje manipulujące danymi.

Omówimy je teraz po kolei.

Kontrola kompilatora

Kompilator C# musi wiedzieć pewne rzeczy na temat naszego programu, np. jakich zewnętrznych zasobów będziemy potrzebować. Możemy także poinformować kompilator o istotnych opcjach dotyczących konstrukcji programu. Niektóre części naszego systemu po prostu udostępnią kompilatorowi te informacje, by wiedział co ma robić.

Przechowywanie danych

Działanie programów polega na przetwarzaniu danych. Podczas przetwarzania dane te muszą być przechowywane gdzieś w komputerze. Wszystkie języki komputerowe obsługują swego rodzaju zmienne. Zmienna to po prostu nazwana lokalizacja, w której znajduje się wartość podczas wykonywania programu. C# umożliwia także tworzenie struktur przechowujących kilka elementów — pojedyncza struktura może więc np. zawierać wszystkie informacje na temat konkretnego klienta banku.

Projektując swój program, będziecie musieli zadecydować jakie dane mają być przechowywane. Musicie wybrać także sensowne nazwy, które będą reprezentować te dane.

Opis rozwiązania

Instrukcje, które opisują rozwiązanie problemu również muszą stanowić część programu. Pojedyncza, prosta instrukcja wskazuje jedną konkretną operację, jaką ma wykonać program — np. dodać dwie liczby i zachować wynik.

To co jest naprawdę intrygujące w programach to to, że niektóre instrukcje mogą zmienić kolejność wykonywania dalszych instrukcji. Nasz program może więc przeanalizować dane i zdecydować co należy zrobić. W przypadku C# możemy połączyć ze sobą kilka instrukcji, by utworzyć część programu odpowiedzialną za wykonywanie jednego konkretnego zadania. Taka zbitka instrukcji nazywana jest metodą.

Doświadczeni programiści rozkładają problem na kilka mniejszych problemów i opracowują metodę dla każdego z nich.

Metody bywają bardzo małe, ale też i bardzo duże. Czasami zwracają wartości, które mogą, lecz nie muszą się nam przydać. Metodzie można nadać dowolną nazwę, a w programie możemy umieścić tyle metod, ile wydaje nam się koniecznym. Metoda może odwoływać się do innych metod. Dla języka C# dostępnych jest również wiele bibliotek. Dzięki temu podczas pisania programów nie musimy za każdym razem wyważać otwartych drzwi. O metodach powiemy szczegółowo nieco później.

Identyfikatory i słowa kluczowe

Każdej utworzonej metodzie nadajemy nazwę, najlepiej odzwierciedlającą wykonywane przez nią zadanie, np. PokazMenu czy ZapiszWPliku. By wykonać program, język C# szuka metody o specjalnej nazwie Main. Metoda ta wywoływana jest podczas uruchamianiu programu, a gdy jej wykonanie dobiega końca, to kończy się również nasz program. Wymyślone przez nas nazwy, za pomocą których identyfikujemy metody to identyfikatory. Identyfikatory tworzymy także wtedy, gdy przechowujemy w czymś wartości. Jeśli więc chcemy przechowywać długość drewna, to dobrym wyborem będzie nazwa dlugoscDrewna. W dalszej części książki pomówimy o zasadach i konwencjach, których należy przestrzegać przy tworzeniu identyfikatorów.

Słowa stanowiące część samego języka C# to tak zwane słowa kluczowe. W przepisie kulinarnym kluczowe mogłyby być słowa „mieszać”, „podgrzewać” czy „do”. Dzięki nim moglibyśmy napisać „podgrzewaćdo rozpuszczenia cukru” czy „mieszać do uzyskania jednolitej konsystencji”. I rzeczywiście, programy komputerowe w dużej mierze przypominają przepisy.

Obiekty

Niektóre rzeczy w pisanych przez nas programach są obiektami, które stanowią część wykorzystywanej przez nas platformy. Kontynuując analogię z gotowaniem, obiekty można porównać do misek czy piecyków, z których korzystamy w trakcie przygotowywania potraw.

Tekst w programie komputerowym

Programy zawierają dwa rodzaje tekstu. Jednym z nich są instrukcje, które mówią komputerowi, co ma zrobić, a drugim napisy, które mają być wyświetlane dla użytkownika. We wspomnianym przepisie na ciasto mama mogłaby dodać jeszcze jeden krok:

Teraz, używając różowego lukru, wykonaj na cieście napis „Wesołych świąt”.

Tekst, który ma pojawić się na cieście został umieszczony przez mamę w cudzysłowie — z takim samym zapisem spotkamy się w języku C#. „Wesołych świąt” to nie instrukcja, lecz sam napis, który mamy wydrukować.

Kolory i konwencje

W niniejszej książce stosowane jest kolorowanie składni, które może wam przypominać wygląd kodu w profesjonalnym edytorze, np. Visual Studio. Kolory mają jedynie za zadanie ułatwić zrozumienie programu, same w sobie nic nie znaczą. Kolor pisanego przez was kodu jest automatycznie ustawiany przez edytor.

Autor: Rob Miles

Tłumaczenie: Joanna Liana