P-programowanie https://www.p-programowanie.pl Blog poświęcony programowaniu, opisujący rozwój kariery w zawodzie programisty. Mon, 22 Apr 2019 13:39:41 +0000 pl-PL hourly 1 https://wordpress.org/?v=5.1.1 Instrukcje warunkowe https://www.p-programowanie.pl/kurs-cpp/instrukcje-warunkowe/ https://www.p-programowanie.pl/kurs-cpp/instrukcje-warunkowe/#respond Sun, 21 Apr 2019 18:40:04 +0000 https://www.p-programowanie.pl/?p=3930 Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Programy napisane do tej pory potrafiły pobierać pewne informacje od użytkownika oraz wyświetlać je na ekran. Niestety, nie byliśmy w stanie uzależnić działania programu od pewnych warunków. Instrukcje warunkowe pozwalają sterować program w różny sposób w zależności od wartości zmiennych decyzyjnych. Dzięki temu, program pisane przez nas programy mogą zacząć „myśleć” i zachowywać się w […]]]>

Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Programy napisane do tej pory potrafiły pobierać pewne informacje od użytkownika oraz wyświetlać je na ekran. Niestety, nie byliśmy w stanie uzależnić działania programu od pewnych warunków. Instrukcje warunkowe pozwalają sterować program w różny sposób w zależności od wartości zmiennych decyzyjnych. Dzięki temu, program pisane przez nas programy mogą zacząć „myśleć” i zachowywać się w różny sposób.

Instrukcja warunkowa IF

Instrukcja warunkowa if jest najprostszą instrukcją warunkową, którą będziesz stosował praktycznie w każdym swoim programie. Za jej pomocą możesz sprawdzić czy dany warunek jest prawdziwy, czy też nie. W zależności od tego Twój program może zachować się w różny sposób.

Składnia instrukcji warunkowej if jest następująca:

if (true) {
    cout << "prawda" << endl;
}

Przeanalizujmy powyższy kod. Po instrukcji warunkowej if występuje nawias, w którym znajduje się jakieś wyrażenie logiczne. Może ono być dowolną zmienną lub bezpośrednią wartością. Wyrażenia logiczne możemy budować korzystając z operatorów porównania. Oto ich lista:

Na powyższym obrazku zostało zaprezentowanych 6 operatorów porównania. Są to operatory obustronne. Oznacza to, że po obu stronach tych operatorów możemy wstawić jakąś wartość lub zmienną.

Kolejną ważną kwestią w instrukcjach warunkowych są klamry zasięgu. Klamry te oznaczają zasięg funkcji lub instrukcji warunkowej. Tak daleko jak sięga klamra, tak daleko będzie wykonywał się kod gdy warunek zostanie spełniony. Po co w programowaniu potrzebne są klamry? Po to abyśmy mogli wykonać wiele operacji w jednej funkcji, lub wiele operacji podczas spełnienia jednego warunku:

Na powyższym obrazku widać, jak analizowany jest warunek. Pełny kod programu może wyglądać następująco:

#include <iostream>

using namespace std;

int main()
{
    if (51 > 30) {
        cout << "tak" << endl;
        cout << "tak" << endk;
    }

    return 0;
}

Gdy warunek jest prawdziwy, wtedy program zacznie wykonywać kod w klamrach, a więc napis tak zostanie wyświetlony dwa razy. Gdyby warunek był nieprawdziwy, wtedy program ominie klamry i zacznie wykonywać kod poza nimi.

W ten sam sposób budujemy różne inne warunki. Przykładowo:

if (1 == 1) {
    cout << "Prawda" << endl;
}

Możemy także korzystać ze zmiennych:

int wiek = 15;

if (wiek < 18) {
    cout << "jestes niepelnoletni!" << endl;
}

Oraz:

bool administrator = true;

if (administrator == true) {
    cout << "tylko dla administratora" << endl;
}

Jak pewnie zauważyłeś, operatory porównania dają nam dużą kontrolę nad biegiem wykonania programu. Prawdziwą siłą są jednak operatory logiczne, zaprezentowane na poniższym rysunku:

Tak jak w przypadku operatorów porównania, tak samo operatory logiczne są operatorami dwustronnymi. Przykładowe ich wykorzystanie może wyglądać następująco:

int liczba = 13;

if (liczba >= 10 && liczba <= 20) {
    cout << "Liczba jest z zakresu 10-20" << endl;
}

W powyższym przykładzie zastosowany został operator &&, dzięki któremu możemy sprawdzić, czy warunki po obu stronach są prawdziwe. Inny przykład:

int wiek = 26;
bool nietrzezwy = true;

if (wiek < 18 || nietrzezwy == true) {
    cout << "Osobom ponizej 18 lub nietrzezwym alko nie sprzedajmy!" << endl;
}

W powyższym przykładzie zastosowany został operator ||, dzięki któremu cały warunek zostanie spełniony, jeżeli po którejkolwiek stronie pojawi się prawda. W tym przypadku tekst zostanie wyświetlony na ekranie, mimo, że wiek osoby to 26 lat.

Słowo kluczowe else

We wszystkich przykładach wyżej kod w klamrach wykona się, jeżeli warunek zostanie spełniony. Do instrukcji warunkowej można dopisać także rozkaz else, który oznacza dosłownie „w przeciwnym wypadku”. Jego zakres także regulujemy klamrami. Zostaną one wykonane, jeżeli warunek instrukcji if jest fałszywy. Przykładowo:

int wiek = 16;

if (wiek >= 18) {
    cout << "jestes pelnoletni" << endl;
} else {
    cout << "jestes niepelnoletni" << endl;
}

Złożone instrukcje warunkowe

Czasem logika naszego programu rośnie, a to zmusi nas do tworzenia coraz bardziej złożonych instrukcji warunkowych. Jednym z ciekawych zabiegów może być dodanie warunku if do rozkazu else. Przykładowo:

string jezyk = "c++";

if (jezyk == "C#") {
    cout << "jestes programista C#";
} else if (jezyk == "c++") {
    cout << "jestes programista C++";
} else if (jezyk == "java") {
    cout << "jestes programista java";
} else {
    cout << "nie wiem co to za jezyk";
}

Złożone instrukcje (if oraz else) są lepsze niż kilka występujących po sobie zwykłych instrukcji warunkowych (if). Dzieje się tak dlatego, że podczas trafia na pasujący warunek reszta kodu nie jest sprawdzana (jest to optymalne), a dodatkowo możemy użyć rozkazu else w przypadku gdy żaden z warunków nie został spełniony.

Zagnieżdżone instrukcje warunkowe

Nic nie stoi na przeszkodzie aby zagnieżdżać instrukcje warunkowe. Jedynym problemem może być utrzymanie czystości kodu i dbanie o poprawne zamykanie klamer zasięgu. Przykładowo:

bool student = true;
int nrPakietu = 1;

if (student == true) {
    if (nrPakietu == 1) {
        cout << "Studencki pakiet 1" << endl;
    } else {
        cout << "Nieznany pakiet" << endl;
    }
} else  {
    if (nrPakietu == 1) {
        cout << "Normalny pakiet 1" << endl;
    } else if (nrPakietu == 2) {
        cout << "Normalny pakiet 2" << endl;
    } else {
        cout << "Nieznany pakiet" << endl;
    }
}

Przydatne warunki

Ucząc się języka C++ warto znać kilka przydatnych warunków, których dość często się używa. Są to m.in. sprawdzanie długości wyrazu, sprawdzanie parzystości liczby. Mogą się one przydać szczególnie jeżeli np. przygotowujesz się do egzaminu maturalnego z informatyki.

Możesz sprawdzić długość dowolnej zmiennej tekstowej używając funkcji length(). W tym celu wywołaj ją na zmiennej używając operatora kropki:

string name = "Karol";
cout << name.length() << endl; // 5

Dzięki temu, możesz np. sprawdzić poprawność numeru PESEL:

string pesel;

cout << "Podaj numer PESEL" << endl;
cin >> pesel;

if (pesel.length() != 11) {
    cout << "niepoprawny pesel";
} else {
    cout << "pesel poprawny";
}

Aby sprawdzić parzystość liczby wystarczy użyć operatora dzielenia modulo. Jego symbolem w języku C++ jest znak %. Dzielenie modulo różni się od zwykłego dzielenia tym, że zwraca nam resztę z dzielenia a nie wynik dzielenia. Każda liczba parzysta podzielona na 2 zwraca resztę z dzielenia równą 0. Korzystając z tej własności można łatwo sprawdzić parzystość liczby:

int liczba;

cout << "Podaj liczbe" << endl;
cin >> liczba;

if (liczba % 2 == 0) {
    cout << "liczba parzysta";
} else {
    cout << "liczba nieparzysta";
}

Instrukcja warunkowa SWITCH

Zwykła instrukcja warunkowa if idealnie naddaje się do sterowania zachowaniem naszego programu. Jej konstrukcja jest prosta, dodatkowo możemy używać rozkazu else aby obsłużyć warunki, których nie przewidzieliśmy.

Problem pojawia się natomiast wtedy, gdy instrukcja warunkowa if zaczyna puchnąć czyli staje się tzw. złożoną instrukcją warunkową. W takim wypadku kod przestaje być czytelny dla programisty. Z pomocą przychodzi instrukcja warunkowa o nazwie switch. Została ona stworzona po to, aby redukować złożone instrukcje warunkowe, a dzięki temu upraszczać nasz kod. Rozważmy poniższy przykład:

int dzien = 3;

if (dzien == 1) {
    cout << "Poniedzialek" << endl;
} else if (dzien == 2) {
    cout << "Wtorek" << endl;
} else if (dzien == 3) {
    cout << "Sroda" << endl;
} else if (dzien == 4) {
    cout << "Czwartek" << endl;
} else if (dzien == 5) {
    cout << "Piatek" << endl;
} else if (dzien == 6) {
    cout << "Sobota" << endl;
} else if (dzien == 7) {
    cout << "Niedziela" << endl;
} else {
    cout << "Niepoprawny dzien tygodnia" << endl;
}

Czy powyższy kod wygląda dla Ciebie przejrzyście? Zapewne nie. Można go uprościć zamieniając go na instrukcję switch. Przykładowy kod:

int dzien = 3;

switch (dzien)
{
    case 1:
        cout << "poniedzialek";
        break;
    case 2:
        cout << "Wtorek" << endl;
        break;
    case 3:
        cout << "Sroda" << endl;
        break;
    case 4:
        cout << "Czwartek" << endl;
        break;
    case 5:
        cout << "Piatek" << endl;
        break;
    case 6:
        cout << "Sobota" << endl;
        break;
    case 7:
        cout << "Niedziela" << endl;
        break;
    default:
        cout << "Niepoprawny dzien tygodnia" << endl;
}

Powyższy kod zadziała w ten sam sposób. Przeanalizujmy jego strukturę:

  • parametrem rozkazu switch jest zmienna decyzyjna, na której opieramy warunki
  • wszystkie warunki obsługujemy za pomocą słów kluczowych case, którym musimy podać także wartość, którą obsługują
  • nie ma klamer zasięgu, a zakres kodu dla danego warunku sięga od słowa kluczowego case: aż do break;
  • na końcu można dodać słowo kluczowe default:, które pełni rolę else (czyli nieobsługiwany warunek)

Instrukcja warunkowa switch w języku C++ obsługuje tylko typy całkowite oraz tzw. typy wyliczeniowe enum. Z tego powodu jest dość rzadko używana przez programistów. Np. nie da się zbudować instrukcji switch, która obsługiwała by zmienną string.

Jeżeli zapomnisz użyć słowa kluczowego break; po warunku, wtedy kod zacznie wykonywać się dalej, skutkiem czego mogą być pewne błędy w działaniu programu. Tę właściwość można wykorzystać też na swoją korzyść tworząc takie konstrukcje, gdzie kilka warunków podpinamy pod jeden kod:

int dzien = 7;

switch (dzien)
{
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
        cout << "Dzien pracujacy" << endl;
        break;
    case 6:
    case 7:
        cout << "Weekend" << endl;
        break;
    default:
        cout << "Niepoprawny dzien tygodnia" << endl;
}

Jak widzisz, w powyższym przykładzie w niektórych miejscach nie występują słowa kluczowe break; dlatego kilka warunków zostanie obsłużonych przez ten sam kod.

]]>
https://www.p-programowanie.pl/kurs-cpp/instrukcje-warunkowe/feed/ 0
Pierwszy duży program https://www.p-programowanie.pl/kurs-cpp/pierwszy-duzy-program/ https://www.p-programowanie.pl/kurs-cpp/pierwszy-duzy-program/#respond Sat, 20 Apr 2019 22:46:09 +0000 https://www.p-programowanie.pl/?p=3912 Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Napisanie pierwszego dużego programu ma na celu usystematyzować Twoją wiedzę z zupełnych podstaw języka C++. Celem tego artykułu będzie stworzenie programu, który przy użyciu zmiennych, strumienia wyjścia i strumienia wejścia pobierze od użytkownika kilka informacji, następnie wykona obliczenia i zwróci pewne podsumowanie. Cel zadania Napisz program, który zapyta użytkownika o imię, nazwisko i rok urodzenia. […]]]>

Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Napisanie pierwszego dużego programu ma na celu usystematyzować Twoją wiedzę z zupełnych podstaw języka C++. Celem tego artykułu będzie stworzenie programu, który przy użyciu zmiennych, strumienia wyjścia i strumienia wejścia pobierze od użytkownika kilka informacji, następnie wykona obliczenia i zwróci pewne podsumowanie.

Cel zadania

Napisz program, który zapyta użytkownika o imię, nazwisko i rok urodzenia. Następnie zapisze te dane do zmiennych. Program musi obliczyć ile lat ma użytkownik. Na końcu program powinien wyświetlić krótkie podsumowanie na temat użytkownika (imię, nazwisko, wiek).

Spróbuj napisać program sam, później przeczytaj resztę artykułu.

Szablon programu

Pisząc każdy konsolowy program w C++ będziemy zaczynać od tego samego, sprawdzonego szablonu znanego z programu „hello world”. Wygląda on następująco:

#include <iostream>

using namespace std;

int main()
{

    return 0;
}

Jest to główna funkcja programu, wartość zwracana i kilka potrzebnych, konfiguracyjnych linijek na samej górze.

Deklaracja zmiennych

Musimy zadeklarować zmienne, w których będziemy przetrzymywać interesujące nas wartości. Lista zmiennych, które potrzebujemy:

  • zmienna tekstowa na imie
  • zmienna tekstowa na nazwisko
  • zmienna liczbowa na rok
  • zmienna liczbowa na wiek

Dodajmy zmienne do naszego programu:

#include <iostream>

using namespace std;

int main()
{
    string name;
    string surename;
    int birthDate;
    int age;
    
    return 0;
}

Zmienne domyślnie będą puste, nie musimy ich inicjalizować, ponieważ nie znamy wartości jakimi moglibyśmy to zrobić. Wszystkie wartości zmiennych muszą pochodzić od użytkownika.

Pobranie informacji od użytkownika i obliczenia

Za pomocą obiektów cin i cout wyświetlimy na ekranie odpowiedni tekst i pobierzemy informacje od użytkownika:

#include <iostream>

using namespace std;

int main()
{
    string name;
    string surename;
    int birthDate;
    int age;

    cout << "Podaj imie: " << endl;
    cin >> name;
    cout << "Podaj nazwisko: " << endl;
    cin >> surename;
    cout << "Podaj rok urodzenia: " << endl;
    cin >> birthDate;


    return 0;
}

Posiadając komplet danych należy obliczyć wiek użytkownika. Jak zostało napisane w poprzedniej lekcji, zmienne podlegają operacjom algebraicznym. Możemy wykonać obliczenia następująco:

age = 2017 - birthDate;

Cały program może wyglądać następująco:

#include <iostream>

using namespace std;

int main()
{
    string name;
    string surename;
    int birthDate;
    int age;

    cout << "Podaj imie: " << endl;
    cin >> name;
    cout << "Podaj nazwisko: " << endl;
    cin >> surename;
    cout << "Podaj rok urodzenia: " << endl;
    cin >> birthDate;

    age = 2019 - birthDate;

    cout << endl;
    cout << "Nazywasz sie " << name << " " << surename << endl;
    cout << "Masz " << age << " lat" << endl;

    return 0;
}

Wynik działania programu:

Podsumowanie

Zmienne oraz obsługa wyjścia/wejścia stanowią absolutną podstawę w programowaniu w języku C++.  Jeżeli nie do końca zrozumiałeś ten zakres materiału przećwicz go, napisz kilka swoich małych programów. Kolejne lekcje programowania w C++ będą wymagały dobrej znajomości tych zagadnień.

Zapraszam Cię do kolejnej lekcji o nazwie instrukcje warunkowe

]]>
https://www.p-programowanie.pl/kurs-cpp/pierwszy-duzy-program/feed/ 0
Wyświetlanie i pobieranie danych https://www.p-programowanie.pl/kurs-cpp/wyswietlanie-pobieranie-danych/ https://www.p-programowanie.pl/kurs-cpp/wyswietlanie-pobieranie-danych/#respond Sat, 20 Apr 2019 22:22:53 +0000 https://www.p-programowanie.pl/?p=3886 Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Wyświetlanie tekstu na ekranie to podstawa podczas interakcji z użytkownikiem. Na takiej samej zasadzie działał nasz przykładowy program „hello world”. Do wyświetlania tekstu stworzone zostały gotowe funkcje. Ich składania jest bardzo prosta i sprowadza się jedynie do przekazania co konkretnie chcemy wyświetlić. Bardzo podobnie wygląda pobieranie danych. Wyświetlanie na ekran – COUT Obiekt cout służy […]]]>

Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Wyświetlanie tekstu na ekranie to podstawa podczas interakcji z użytkownikiem. Na takiej samej zasadzie działał nasz przykładowy program „hello world”. Do wyświetlania tekstu stworzone zostały gotowe funkcje. Ich składania jest bardzo prosta i sprowadza się jedynie do przekazania co konkretnie chcemy wyświetlić. Bardzo podobnie wygląda pobieranie danych.

Wyświetlanie na ekran – COUT

Obiekt cout służy do wyświetlania danych na ekranie konsoli. Jeżeli ta nazwa wydaje Ci się dziwna to wiedz, że pochodzi od słów „console output„. W naszym programie „hello world” za jej pomocą został wyświetlony napis powitalny:

#include <iostream>

using namespace std;

int main()
{
    cout << "Hello world!" << endl;
    return 0;
}

Obiekt cout reprezentuje wyjście konsoli (czyli to co widzimy – czarne okienko). Następnie za pomocą operatora nazywanego strumieniem wyjścia << możemy na wyjście przekazać dowolną wartość. Może być to tekst, liczba lub jakaś istniejąca zmienna:

cout << "some text" << endl;

Dzięki przekazaniu na wyjście obiektu endl zostanie wstawiony znak nowej linii. Nic nie stoi na przeszkodzie aby cout wywołać wiele razy (każde wywołanie w nowej linii):

cout << "some text1" << endl;
cout << "some text2" << endl;
cout << "some text3" << endl;
cout << 12345 << endl;

Wynikiem takiej operacji będzie kilka napisów każdy w nowej linii, na samym dole zobaczymy także liczbę.

Zwróć uwagę, że każda linia jest zakończona znakiem średnika. Jest to bardzo ważne w języku C++. Jeżeli zapomnisz średnika kompilator zgłosi Ci błąd error: expected ‚;’ before ‚cout’. Ogólna zasad brzmi, że średnik musi być zakończeniem każdej linii kodu w języku C++.

Za pomocą cout możemy wyświetlać także zmienne:

string name = "Karol";
cout << "Hello " << name;

Za pomocą operatora strumienia możemy łączyć wiele wartość i wiele zmiennych razem:

string name = "Karol";
int age = 28;

cout << "Hello " << name << ". You are " << age << " years old!";

Możesz sprawdzić jak działa program, wystarczy że uruchomisz poniższy kod:

#include <iostream>

using namespace std;

int main()
{
    string name = "Karol";
    int age = 28;

    cout << "Hello " << name << ". You are " << age << " years old!";
    return 0;
}

Jest to przerobiona wersja naszego programu „hello world”.

Podczas wyświetlania na ekran możemy wyświetlić każdy rodzaj zmiennej, a nie tylko zmienne tekstowe. Jeżeli przekażesz na wyjście zmienną liczbową lub typ logiczny, wtedy zostanie on automatycznie (niejawnie) zamieniony na tekst i wtedy wyświetlony użytkownikowi.

Wczytywanie z ekranu – CIN

Obiekt cin służy do pobierania danych z konsoli (od użytkownika). Tak samo jak w przypadku cout jego nazwa pochodzi od słów „console input„, a obiekt reprezentuje wszystkie wartości „przychodzące”. Skoro parametrem dla strumienia wyjściowego była wartość tekstowa, liczbowa lub zmienna, co będzie parametrem dla obiektu strumienia wejściowego? Oczywiście zmienna.

Użycie obietu cin spowoduje pobranie wartości od użytkownika, ta wartość musi gdzieś zostać zapisana. Jedną możliwością jest zapisanie jej do jakiejś zmiennej. Możemy to zrobić za pomocą operatora strumienia wejścia >> (strzałki skierowane odwrotnie niż przy cout).

Przykładowo:

string name;
cout << "Podaj imie:" << endl;
cin >> name;

Ten prosty kod spowoduje utworzenie nowej zmiennej tekstowej, wyświetlenie napisu na konsolę i pobranie wartości od użytkownika z jednoczesnym zapisaniem tej wartości do zmiennej name.

Obsługa strumienia

Znając obiekty cin oraz cout oraz operatory strumienia wyjścia << i strumienia wejścia >> możesz łatwo pisać funkcjonalne programy. Zauważ, że bardzo łatwo zapamiętać symbole strumienia << oraz >>. Możesz sobie wyobrazić, że symbolizują one kierunek przepływu danych:

Operatory wyjścia i wejścia muszą zawsze zostać użyte tak jak na powyższym obrazku. Tak jak wspomniałem wcześniej, symbolizują one kierunek przepływu danych.  Kilka zasad:

  • strumień wyjścia (<<) zawsze musi kierować dane na konsolę (cout) (lub ewentualnie jakiegoś pliku)
  • strumień wejścia (>>) zawsze musi kierować dane od użytkownika (cin) do jakiejś zmiennej

Jeżeli zastanowisz się nad strzałkami z powyższego obrazka, wszystko powinno być dla Ciebie jasne i łatwe do zapamiętania. Obiekty CIN i COUT reprezentują to co widzi lub to co wpisuje użytkownik, a operator strumienia ustala kierunek przepływu.

Prosty demonstracyjny program:

#include <iostream>

using namespace std;

int main()
{
    string name;

    cout << "Podaj imie:" << endl;
    cin >> name;

    cout << "Nazywasz sie: " << name << endl;

    return 0;
}

Działający program wygląda następująco:

To już wszystko! Zapraszam do kolejnej lekcji o nazwie pierwszy duży program

]]>
https://www.p-programowanie.pl/kurs-cpp/wyswietlanie-pobieranie-danych/feed/ 0
Zmienne https://www.p-programowanie.pl/kurs-cpp/zmienne/ https://www.p-programowanie.pl/kurs-cpp/zmienne/#respond Sat, 20 Apr 2019 21:48:46 +0000 https://www.p-programowanie.pl/?p=3893 Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Zmienne są podstawowym bytem w każdym języku programowania. Pozwalają przechowywać dowolne wartości w pamięci naszego programu. Bez zmiennych nie dałoby się napisać żadnej sensownej aplikacji. Upewnij się, że bardzo dobrze zrozumiałeś pojęcie zmiennej przed przejściem do kolejnych etapów kursu. Czym jest zmienna Zmienna to konstrukcja pozwalająca zapisać coś do pamięci naszego programu, a później to […]]]>

Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Zmienne są podstawowym bytem w każdym języku programowania. Pozwalają przechowywać dowolne wartości w pamięci naszego programu. Bez zmiennych nie dałoby się napisać żadnej sensownej aplikacji. Upewnij się, że bardzo dobrze zrozumiałeś pojęcie zmiennej przed przejściem do kolejnych etapów kursu.

Czym jest zmienna

Zmienna to konstrukcja pozwalająca zapisać coś do pamięci naszego programu, a później to odczytać. Wyróżniamy różne typy zmiennych w zależności od rodzaju informacji, jakie będą przechowywać. Podstawowe typy to:

  • bool – przechowująca wartość true lub false. Innymi słowy może zwrócić nam albo prawdę, albo fałsz.
  • char – przechowująca pojedynczy znak
  • int – przechowująca liczbę całkowitą (max. 2.147.483.647)
  • float – przechowująca liczbę zmiennoprzecinkową (max dokładność 7 cyfr)
  • double – przechowująca liczbę zmiennoprzecinkową (max dokładność 15 cyfr)
  • string – przechowująca dowolny ciąg znaków

Każda zmienna musi składać się z:

  • typu zmiennej – aby określić jakie dane będzie przechowywać
  • nazwy zmiennej – będziemy jej używać chcąc dostać się do zmiennej (nazwa musi być unikalna)
  • opcjonalnie zmienną można zainicjalizować, tzn. nadać zmiennej wartość początkową

Deklarując zmienne tworzymy w pamięci naszego programu „pojemniki”, które umożliwią przechowywanie pewnych wartości. Zmienne są używane aby przechowywać dane wpisane przez użytkownika, przetrzymywać wyniki różnych operacji lub przechowywać treść wczytaną z pliku.

Tworzenie zmiennych

Aby zadeklarować nową zmienną należy napisać typ zmiennej a następnie jej nazwę, np.:

string name;

Powyższy zapis oznacza utworzenie nowej zmiennej typu string o nazwie imie. Powyższa zmienna będzie pusta. Aby nadać jej wartość musimy użyć operatora przypisania czyli znaku równości:

string name;
name = "Karol";

Powyższy zapis oznacza utworzenie nowej zmiennej i nadanie jej wartości „Karol”. Zapis możemy skrócić do jednej linii, deklarując zmienną i od razu zainicjalizować ją jakąś wartością np.:

string name = "Karol";

Zwróć uwagę, że wartości tekstowe muszą być w C++ otaczane w cudzysłowy. Inaczej jest z liczbami, których nie należy umieszczać w cudzysłowach.

Wartości tekstowe w C++ umieszczamy w cudzysłowach, wartości liczbowe pozostawiamy bez cudzysłowów.

Zadeklarujmy jeszcze kilka zmiennych:

string name = "Karol";
string surename = "Trybulec";

int phone = 123456;

bool isBoy = true;
bool isGirl = false;

string details;

Zauważ powtarzającą się regułę:

  • na pierwszym miejscu zawsze typ zmiennej
  • na drugim miejscu zawsze nazwa zmiennej
  • później możesz przypisać wartość, ale nie musisz
  • każda linia kończy się cudzysłowem

Operacje na zmiennych

Zmienne podlegają zasadom pewnej arytmetyki. Przykładowo zmienne liczbowe można do siebie dodawać:

int number1 = 3;
int number2 = 6;

int number3 = number1 + number2;

W powyższym przykładzie zmienna liczba3 będzie miała wartość 9. Stanie się tak dlatego, że została ona zainicjalizowana sumą wartości dwóch poprzednich zmiennych. Podobnie na zmiennych działają inne operatory takie jak: odejmowanie, mnożenie i dzielenie.

Co ciekawe operator dodawania dla zmiennych tekstowych spowoduje „sklejenie” ze sobą dwóch stringów. W programowaniu taki proces nazywamy fachowo konkatenacją. Przykładowo:

string text1 = "hello";
string text2 = "world!";

string text3 = text1 + " " + text2;

Czy domyślasz się jaka będzie wartość zmiennej text3? Zwróć uwagę, że oprócz konkatenacji dwóch zmiennych, dołożony został pusty znak spacji, to także jest dozwolone, ponieważ wartość tekstowa umieszczona w cudzysłowie jest tego samego typu co zmienna (czyli tekst).

Niedozwolone operacje

Niedozwolone są natomiast próby przypisania wartości do zmiennych, które nie są zgodne z jej typami. Oznacza to, że do zmiennej tekstowej nie możemy przypisać nic oprócz tekstu, a do zmiennej liczbowej nic oprócz liczby. Przykładowo:

string text1 = "hello world!"; // ok
string text2 = "hello apollo" + "13"; // ok

int number1 = "karol"; // error!
string text3 = 13; // error!
string text4 = "karol" + 13; // error!

W powyższym przykładzie nie możemy dopisać liczby 13 do zmiennej tekstowej. Nic nie stoi jednak na przeszkodzie, aby wartość liczbową potraktować jako tekst, wtedy przypisania jest możliwe!

W powyższym przykładzie użyty został komentarz. Wszystko co znajduje się za dwoma ukośnymi kreskami (//) jest komentarzem, i nie jest brane pod uwagę przez kompilator.

Zmienne stałe

Tytuł tego akapitu brzmi dość paradoksalnie. W języku C++ możliwa jest deklaracja zmiennych, których wartości nie da się później zmienić. Aby zadeklarować taką zmienną należy poprzedzić jej typ przedrostkiem const. Przykładowo:

const float PI = 3.14;

W powyższym przykładzie zadeklarowaliśmy zmienną przechowującą liczbę zmiennoprzecinkową. Jest to zmienna stała, nie można zmienić jej wartości. Próba zmiany wartości zmiennej stałej spowoduje błąd kompilacji programu.

Pisząc programy czasem chcemy zablokować możliwość zmiany wartości danej zmiennej. Idealnym przykładem mogą być np. stałe matematyczne. Wartość liczby PI nigdy nie ulegnie zmianie, dlatego taka zmienna powinna być jak najbardziej oznaczona jako stała:

const float PI = 3.14;
int r = 6;

float obwod = 2 * PI * r; // 37.68

To już wszystko! Zapraszam Cię do kolejnej lekcji o nazwie wyświetlanie i pobieranie danych

]]>
https://www.p-programowanie.pl/kurs-cpp/zmienne/feed/ 0
Pierwszy program https://www.p-programowanie.pl/kurs-cpp/pierwszy-program/ https://www.p-programowanie.pl/kurs-cpp/pierwszy-program/#respond Sat, 20 Apr 2019 20:36:09 +0000 https://www.p-programowanie.pl/?p=3868 Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Uruchomienie pierwszego programu w C++ jest jak pierwsza cegiełka nowo budowanego domu. Przed Tobą dużo pracy, jednak sam fakt, że uruchomiłeś pierwszy program w danym języku, to ważny pierwszy krok. Najmniejszy możliwy program do uruchomienia w danej technologii nosi potocznie nazwę „Hello world”. Jest to najmniejszy możliwy program, który będzie działał i wyświetli taki napis […]]]>

Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Uruchomienie pierwszego programu w C++ jest jak pierwsza cegiełka nowo budowanego domu. Przed Tobą dużo pracy, jednak sam fakt, że uruchomiłeś pierwszy program w danym języku, to ważny pierwszy krok. Najmniejszy możliwy program do uruchomienia w danej technologii nosi potocznie nazwę „Hello world”. Jest to najmniejszy możliwy program, który będzie działał i wyświetli taki napis użytkownikowi.

Pierwszy program w C++

W poprzedniej lekcji zainstalowaliśmy środowisko programistyczne Code::Blocks i utworzyliśmy pierwszy projekt. Jeżeli tego nie zrobiłeś, utwórz teraz nowy projekt typu „Console application” dla języka „C++” wybierając kompilator „GNU GCC Compiler„. Twoim oczom powinien ukazać się poniższy kod:

#include <iostream>

using namespace std;

int main()
{
    cout << "Hello world!" << endl;
    return 0;
}

Jest to tzw. kod „hello world”, czyli najkrótszy możliwy program w danym języku programowania, który po uruchomieniu zadziała i wyświetli napis powitalny. Nie martw się tym, że na razie nic nie rozumiesz.

Uruchomienie programu

Kiedy tworzysz nowy program w jakimkolwiek języku piszesz jego kod źródłowy posługując się zwykłym tekstem. Następnie, kod źródłowy musi zostać skompilowany do pliku wykonywalnego za pomocą innego narzędzia zwanego kompilatorem.

Kompilator to narzędzie, które zamienia kod źródłowy programu na plik wykonywalny. Po skompilowaniu kodu źródłowego możesz uruchomić swój program.

W Code::Block możesz skompilować i uruchomić kod za pomocą ikonek na pasku narzędzi (opisane w poprzedniej lekcji) lub skrótem klawiaturowym F9. W celu skompilowania i uruchomienia naszego pierwszego programu naciśnij właśnie ten skrót klawiaturowy.

Twoim oczom powinien ukazać się pierwszy program. Jest to czarna konsola z napisem „Witaj świecie”. Nie zniechęć się czarnym oknem konsoli. Konsola jest podstawowym narzędziem dzięki, dzięki któremu możemy mieć jakąś interakcję z użytkownikiem komputera. Na aplikacje okienkowe przyjdzie czas kiedyś.

Gdybyś chciał programować zaczynając od razu od ładnych „windowsowych” okienek, najprawdopodobniej Ci się nie uda. Jest to spowodowane tym, że samo zaprogramowanie takiego okienka wymaga dziesiątek linii kodu. Zaczynając naukę programowania naturalnym pierwszym etapem nauki jest poznanie języka właśnie poprzez konsolowe aplikacje.

Lokalizacja programu

Twój program skompilował się i uruchomił. Jest to Twój pierwszy działający program w języku C++. Jeżeli chcesz wysłać komuś swój program oczywiście możesz to zrobić. Po procesie zwanym kompilacją (zamiana kodu programu na plik wykonywalny) program znajduje się w katalogu Twojego projektu w folderze „bin

Skompilowane programy lądują w podkatalogu „bin” znajdującym się w tej samej lokalizacji co utworzony przez nas projekt. Plik ma rozszerzenie *.exe i może zostać uruchomiony już bez korzystania z Code::Blocks.

Porządek w kodzie programu

Podczas pisania programów bardzo ważne jest, aby utrzymywać porządek w kodzie źródłowym. Wszystkie klamerki i wcięcia są absolutnie wymagane. Kompilator potrafi wybaczyć Ci nieczytelny kod (brak wcięć, złe wyrównanie), jednak nie wybaczy Ci brakującej klamerki. Kompilacja takiego programu zakończy się błędem.

Jako początkujący programista nie znasz jeszcze składni języka C++ – ale nie martw się, poznasz ją z czasem. Na początku bardzo pomoże Ci narzędzie wbudowane w Code::Blocks służące do „upiększania” kodu.

Narzędzie nazywa się „Source code formatter” i jest dostępne w menu „Plugins”. Zachęcam Cię, abyś pisząc swoje programu od czasu do czasu formatował kod tym narzędziem. Później, gdy poznasz składnie języka nie będziesz potrzebował już pomocy dodatku.

Struktura programu

Aktualnie prawdopodobnie nie rozumiesz kodu programu „hello world” ale nie martw się tym, wszystko wyjaśni się w innych lekcjach. Najważniejsze co musisz zapamiętać to fakt, że jest to Twój bazowy szkielet, na którym musisz pracować. Każdy program w C++ musi zawierać główną funkcję programu o nazwie main, a na jej końcu wartość zwracaną return 0; Wszystko co dopisujesz do programu dopisuj właśnie pomiędzy dwoma klamrami funkcji main pamiętając, aby nie usunąć ostatniej linii return. Pamiętaj także, że każda linia w C++ musi kończyć się średnikiem.

Zapraszam Cię do kolejnej lekcji o nazwie zmienne

]]>
https://www.p-programowanie.pl/kurs-cpp/pierwszy-program/feed/ 0
Środowisko pracy https://www.p-programowanie.pl/kurs-cpp/srodowisko-pracy/ https://www.p-programowanie.pl/kurs-cpp/srodowisko-pracy/#respond Sat, 20 Apr 2019 19:57:53 +0000 https://www.p-programowanie.pl/?p=3845 Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Zanim zaczniesz programować w C++ musisz zdecydować się na konkretne środowisko programistyczne, w jakim będziesz pracował. Środowisko programistyczne to program, który pozwala Ci pisać kod źródłowy a następnie w wygodny sposób ten kod uruchamiać. Środowisko programistyczne przypomina zaawansowany edytor tekstu z udogodnieniami związanymi z danym językiem programowania, dla jakiego zostało stworzone. Wybór środowiska programistycznego Środowisko […]]]>

Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Zanim zaczniesz programować w C++ musisz zdecydować się na konkretne środowisko programistyczne, w jakim będziesz pracował. Środowisko programistyczne to program, który pozwala Ci pisać kod źródłowy a następnie w wygodny sposób ten kod uruchamiać. Środowisko programistyczne przypomina zaawansowany edytor tekstu z udogodnieniami związanymi z danym językiem programowania, dla jakiego zostało stworzone.

Wybór środowiska programistycznego

Środowisko programistyczne to rozbudowany edytor tekstu, z wieloma funkcjami pomagającymi w programowaniu. Można powiedzieć, że jest to edytor tekstowy z wbudowanymi komendami służącymi do uruchamiania Twoich programów, oraz kilkoma innymi usprawnieniami.

Najbardziej popularne środowiska programistyczne dla C++ to:

  • Microsoft Visual Studio – zaawansowane środowisko używane przez profesjonalnych programistów (płatne)
  • Code::Blocks – dość proste środowisko, idealne do nauki programowania, często używane na maturach z informatyki (darmowe)
  • Visual Studio Code – dość nowe, bardzo intuicyjne środowisko programistyczne z wieloma funkcjami, niestety wymaga dodatkowej konfiguracji zanim będziesz mógł zacząć pracę (darmowe)
  • Dev-C++ – stare i niestety ciągle popularne, stanowczo odradzam używania tego środowiska (darmowe)

Na potrzeby kursu idealny do nauki będzie Code::Blocks i to właśnie w nim będę pisał wszystkie programy.

Instalacja Code::Blocks

Code::Blocks jest darmowym środowiskiem programistycznym, działającym na systemach Windows oraz Linux. Aktualnie wspierane są wszystkie wersje systemu Windows. W celu pobrania programu wchodzimy na stronę http://www.codeblocks.org/downloads/26 i pobieramy dedykowaną dla naszego systemu.

Bardzo ważne aby pobrać wersję instalacyjną o nazwie codeblocks-17.12mingw-setup.exe. Zawiera ona MinGW czyli kompilator języka C++. Pobierając inną wersję instalacyjną nie będziesz mógł uruchomić żadnego napisanego przez siebie programu.

Instalacja programu jest bardzo prosta i intuicyjna, nie wymaga żadnej dodatkowej konfiguracji. Wystarczy akceptować wszystkie regulaminy, a po instalacji środowisko będzie gotowe do pracy.

Problemy z wykryciem MinGW

Jeżeli Twoja wersja Code::Blocks ma problemy z wykryciem kompilatora języka C++ (MinGW) oznacza to prawdopodobnie brakujące wpisy w zmiennych środowiskowych.

Najłatwiej naprawić Code::Blocks resetując ustawień środowiska programistycznego. W tym celu kliknij myszką na „Settings” -> „Compiler..” i kliknij przycisk „Reset defatults„. Pojawi się kilka komunikatów, wszystkie musisz potwierdzić. W tym momencie Code::Blocks sam wykryje i ustawi MinGW.

Utworzenie nowego projektu

Po instalacji CB utwórz nowy projekt typu C++. W tym celu kliknij na „File” -> „New” -> „Project„. Z listy projektów wybierz „Console application„. W następnym oknie wybierz język „C++„. Uzupełnij wszystkie pola takie jak nazwa projektu i lokalizacja, gdzie zostanie zapisy.

W zakładce odpowiedzialnej za kompilator nie zmieniaj żadnych ustawień, domyślnie powinien być wybrany „GNU GCC Compiler„. Gratulację, udało Ci się utworzyć pierwszy projekt!

Interfejs Code::Blocks

Oto podstawowy wygląd środowiska Code::Blocks:

Na powyższym obrazku zaznaczyłem najważniejsze elementy środowiska:

  • pliki projektu – czyli pliki z kodem źródłowym Twojego programu
  • kod programu – miejsce, gdzie piszesz kod
  • błędy kompilacji – bardzo ważna sekcja, w której wyświetlą się wszystkie błędy w Twoim kodzie. Dopóki ich nie naprawisz nie uruchomisz programu
  • kompilacja – trzy przyciski, pierwszy kompiluje program, drugi uruchamia program, trzeci robi obydwie akcje jednocześnie

To już wszystko! Zapraszam Cię do kolejnej lekcji o nazwie pierwszy program

]]>
https://www.p-programowanie.pl/kurs-cpp/srodowisko-pracy/feed/ 0
Jak przebranżowić się na programistę – 6 kroków https://www.p-programowanie.pl/studia-praca/jak-przebranzowic-sie-na-programiste-6-krokow/ https://www.p-programowanie.pl/studia-praca/jak-przebranzowic-sie-na-programiste-6-krokow/#comments Thu, 18 Apr 2019 22:06:57 +0000 https://www.p-programowanie.pl/?p=3784 Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Jeżeli wykonywanie obecnego zawodu nie sprawia Ci satysfakcji, lub wydaje Ci się, że zarabiasz zbyt mało, ten artykuł może Cię zainteresować. Postaram się w najlepszy możliwy sposób opisać jak skutecznie możesz przebranżowić się na programistę. Nie wymaga to żadnego nakładu finansowego ani podejmowania studiów informatycznych. W większości przypadków wystarczy systematyczna praca. Przebranżowienie na programistę jest […]]]>

Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Jeżeli wykonywanie obecnego zawodu nie sprawia Ci satysfakcji, lub wydaje Ci się, że zarabiasz zbyt mało, ten artykuł może Cię zainteresować. Postaram się w najlepszy możliwy sposób opisać jak skutecznie możesz przebranżowić się na programistę. Nie wymaga to żadnego nakładu finansowego ani podejmowania studiów informatycznych. W większości przypadków wystarczy systematyczna praca. Przebranżowienie na programistę jest w zasięgu ręki i z odpowiednim podejściem nie powinno zająć Ci dłużej niż rok.

Dlaczego warto się przebranżowić

Jeżeli ktoś lubi pracę biurową, praca programisty wydaje się być dla niego idealna. Programowanie potrafi być pracą bezstresową, dającą jednocześnie ogromną satysfakcję. Jest to zawód bardzo dobrze naddający się dla kobiet, choć paradoksalnie, kobiet w IT jest bardzo mało. Szczególnie na stanowiskach typowo programistycznych.

Szereg benefitów

Większość firm działających w obszarze IT to przeważnie naprawdę dochodowe biznesy. Biura, sprzęt i całe środowisko pracy są dzięki temu doinwestowane i na wysokim poziomie. Pracownicy firm IT praktycznie zawsze posiadają szereg benefitów, których próżno szukać gdzie indziej. Są to m.in. darmowe owoce, kawa, herbata i wszystko co znajduje się w kuchni, pokoje do relaksu, prywatna opieka zdrowotna, wysokie permie, darmowe szkolenia, sprzęt wysokiej jakości, ergonomiczne krzesła, klimatyzowane biura, możliwość podróży, i wiele innych. Czasem porównuję takie miejsca do szkół (praca nauczyciela) lub przychodni/szpitali (praca lekarza). Tam często ze ścian odpadają płytki, a człowiek siedzi na starym drewnianym taborecie. Wtedy stwierdzam, że ciężko o tak dobre warunki pracy gdziekolwiek.

Dodatkowo, w większości firm obowiązują elastyczne godziny pracy, więc tak naprawdę można bardzo wygodnie godzić życie zawodowe z różnymi obowiązkami. Po zdobyciu odpowiedniego doświadczenia można także pracować zdalnie z domu, co jest niesamowitym dodatkowym atutem.

Wysokie zarobki

Zawód programisty jest w obecnych czasach zawodem bardzo dobrze płatnym. Według agencji Sedlak & Sedlak mediana zarobków młodszego programisty to 5500zł brutto a eksperta nawet 14000zł brutto (statystyki z 2018r.). Wydaje mi się, że w dużych miastach i przy sprzyjających okolicznościach (odpowiednie technologie) to wynagrodzenie potrafi być znacznie większe. Można z całą pewnością przyznać, że takich zarobków w naszym kraju nie da się osiągnąć w żadnym innym zawodzie przy jednoczesnym tak małym progu wejścia do branży.

Proces przebranżowienia

Ostatnio pomagałem przebranżowić się mojej dziewczynie. Tak jak wielu innych ludzi w Polsce, stwierdziła, że pomimo posiadania technicznego wykształcenia wyższego nie jest w stanie znaleźć pracy w naszym kraju. Korzystając z własnego doświadczenia i wniosków z rozmów z innymi programistami, starałem się znaleźć optymalną ścieżkę przebranżowienia. Poniżej opiszę ją w 6 prostych krokach.

Krok 1. – wybierz obszar

Programowanie jest zbyt duże i rozległe aby uczyć się wszystkiego. Musisz zdecydować się na pewną wąską dziedzinę, w której zaczniesz się szkolić. Przede wszystkim powinien być to wybór pomiędzy front-endem a back-endem. Czym różni się jedno od drugiego opisałem w innym wpisie o nazwie jak nauczyć się programowania. Po krótce dodam tylko, że łatwiej i szybciej dasz radę nauczyć się front-endu. Aby opanować front-end będziesz potrzebował mniej wiedzy teoretycznej z IT.

Wybór powinien być podyktowany także Twoimi predyspozycjami. Decydując się na front-end fajnie mieć jakieś poczucie estetyki. Z kolei programista back-endu powinien mieć predyspozycje do analitycznego myślenia.

Powyższej przedstawiłem przykładowe zestawy technologii jakie musisz umieć celując w dany zawód. Są to tylko moje przykłady, istnieje o wiele więcej stanowisk. Wszystkie przedstawione przeze mnie zestawy (ang. technology stacks) dotyczą wytwarzania aplikacji internetowych. Wydaje mi się, że w obecnych czasach lepiej celować właśnie w aplikacje internetowe (ang. web applications) niż aplikacje okienkowe (ang. desktop applications). Trend jest obecnie taki, że firmy wszystko przenoszą do chmur i internetu.

Zdecydowanie odradzam natomiast język C++, jest to język niskiego poziomu, obecnie dość niszowy, do nietypowych zastosowań. Nie naddaje się dla kogoś kto chce się przebranżowić, jest raczej dla specjalistów w branży. Nie polecałbym też PHP, Visual Basica, Pascala i języków funkcyjnych (chyba, że wiesz w co celujesz i z jakiegoś powodu są Ci potrzebne).

Krok 2. – ustal ile czasu poświęcisz na naukę

Moim skromnym zdaniem, jesteś w stanie przebranżowić się w okresie od 3-15 miesięcy. Celujesz w wąską dziedzinę, w jedną konkretną technologię lub język. Nauka programowania jest trudna, jednak to nie są chińskie znaki. Im więcej będziesz ćwiczyć tym bardziej wszystko zacznie się sklejać w jedną logiczną całość.

Jeżeli myślisz o przebranżowieniu się na programistę na poważnie, obierz strategię nauki:

  • nauka 8h dziennie oprócz weekendów – idealne rozwiązanie jeżeli jeszcze nie pracujesz, albo możesz sobie pozwolić nie pracować. Daj z siebie wszystko podczas nauki programowania, siedź nad tym cały dzień, tak jakbyś wykonywał jakieś obowiązki w pracy. Po 5h nauki nie chcę Ci się uczyć? Czy tak samo powiedziałbyś w pracy?
  • nauka po pracy i w weekendy – bądź świadomy, że będzie to naprawdę ciężkie a nauka zajmie Ci o wiele dłużej. Sam łączyłem pracę na pełny etat (programowanie) ze studiami na uczelni w trybie dziennym (Politechnika Krakowska). Trwało to około 2-3 lata. Chwilami byłem zmuszony przeskakiwać na 4/5 etatu a i tak nie było mnie w mieszkaniu całymi dnami. Taki tryb życia nie służy nauce.

Jeżeli masz taką możliwość, ucz się programowania całymi dnami – gwarantuję Ci sukces. Jeżeli musisz pracować wiedz, że zajmie Ci to 2-3 razy dłużej – o ile po drodze się nie zniechęcisz. Nawet jeżeli obiecasz sobie systematyczną naukę po pracy, to nie oszukasz zmęczenia ani nie wydłużysz doby.

Powyższa infografika jest trywialna, ale dobrze obrazuje jak ciężko znaleźć czas na naukę łącząc to z innymi obowiązkami. Wypełnianie czasu wolnego nauką (np. w weekendy) oczywiście da Ci dodatkowy czas spędzony na przyswajaniu wiedzy, ale jednocześnie będzie działać bardzo zniechęcająco na dłuższą metę.

Łatwo wyciągnąć wnioski. Szacujmy, że nauka programowania w pierwszym przypadku zajmie Ci 6 miesięcy. Łącząc naukę z pracą zajmie Ci to 10 miesięcy, a jeżeli odliczysz weekendy to prawie dwa lata. Jakiejkolwiek drogi byś nie wybrał – postaw na systematyczność. Ustal sobie grafik i za wszelką cenę staraj się go przestrzegać. Systematyczność jest bardzo ważna w nauce programowania, a odrywając się od tej tematyki na 2-3 dni prawdopodobnie zapomnisz czego uczyłeś się kilka dni temu.

Jeżeli sądzisz, że moje szacowanie (6 miesięcy) nie jest realne, weź pod uwagę, że to nie ważne. Jakąkolwiek liczbę wymyślisz, chodzi raczej o liniową zależność wydłużającego się okresu nauki, a nie konkretną liczbę z dokładnością +/- kilku miesięcy.

Krok 3. – źródło wiedzy

Jeżeli wybrałeś konkretną technologię oraz przyjąłeś jakąś strategię nauki, zastanów się skąd będziesz czerpał wiedzę. Decydując się na naukę programowania musisz umieć uczyć się sam. Nie ma innej możliwości. Zdecydowanie się na zawód programisty to zdecydowanie się na ciągłą i systematyczną naukę i poszerzanie swojej wiedzy.

Gdybym miał zdefiniować programowanie komuś kto nie ma o nim zielonego pojęcia, powiedziałbym, że jest to skuteczność z jaką potrafisz rozwiązywać problemy (ang. problem solving) z użyciem internetu i ogólnodostępnych narzędzi (języków programowania). Nikt nie broni Ci używać internetu i szukać gotowych rozwiązań (to, że czegoś nie umiesz nie jest problemem). Problem zaczyna się wtedy kiedy czegoś nie umiesz, a dodatkowo nie masz zielonego pojęcia jak znaleźć rozwiązanie.

Moim skromnym zdaniem szkoły programowania to strata czasu. Niezastąpionymi źródłami wiedzy są natomiast:

  • książki o danej technologii/języku
  • filmy w postaci kursów
  • artykułu ogólnodostępne w internecie (jak mój blog)

Dlaczego nie szkoły programowania? Przede wszystkim są bardzo drogie. Po drugie, kurs wcześniej czy później się kończy a Ty znowu wracasz do punktu wyjścia – musisz nauczyć się uczyć samemu. Wykładowca nie poświęci Ci całego swojego czasu, a na dodatek jak w każdej szkole mogą występować duże nierówności w grupie.

Pieniądze, które przeznaczyłbyś na szkołę programowania, proponuję Ci wydać na książki lub kursy wideo. Uwierz mi na słowo, są takie działy w programowaniu, które rozumie się po pierwszym przeczytaniu. Są także takie, do których osobiście wracam co kilka miesięcy i przed każdą rozmową rekrutacyjną – a później znowu zapominam jak „to” działało. Taka jest zaleta książki oraz kursu wideo, możesz wiele razy się do niej wrócić, możesz czytać tyle razy, aż zrozumiesz.

Krok 4. – naucz się systemu kontroli wersji (GIT)

System kontroli wersji to absolutna podstawa dla każdego, kto chce rozpocząć przygodę z programowaniem. Co to jest? Typowy projekt programistyczny trwa od kilku tygodni do kilkunastu miesięcy. Przeważnie nad jednym projektem pracuje kilku programistów, choć zdarzają się też duże systemy nad którymi pracują dziesiątki programistów przez wiele lat.

Czy wyobrażasz sobie jak ciężko integrować ze sobą pracę wielu ludzi? Programiści często (nieświadomie) nawzajem usuwają lub „psują” własne, istniejące już, fragmenty kodu. Często dopisując nową funkcjonalność przypadkowo dotykają funkcjonalności, którą zrobił wcześniej ktoś inny. Takie integracje dużych ilości kodu (w postaci zwykłego tekstu) trzeba integrować ze sobą jakimś narzędziem. Właśnie do tego służą systemy kontroli wersji.

Oprócz tego, systemy kontroli wersji zapewniają wersjonowanie oraz wspierają przenoszenie kodu pomiędzy komputerami. Dodatkowo stanowią zabezpieczenie istniejącego kodu przed jego utratą (np. z powodu awarii dysku twardego). Generalnie rzecz biorąc, jest to narzędzie niezbędne w codziennej pracy – nie ważne czy będziesz młodszym czy starszym programistą.

Najpopularniejszym programem do kontroli wersji jest GIT. Można go obsługiwać zarówno z linii poleceń (polecam) jak i graficznymi programami, które możesz zainstalować w systemie Windows. Warto także założyć konto w systemie github.com, gdzie za darmo można tworzyć własne projekty i przechowywać kod. Oprócz tego możesz także ćwiczyć działanie GITa przed rozpoczęciem pracy programisty.

Jeżeli chcesz przebranżowić się na programistę powinieneś stworzyć własne repozytorium kodu w systemie github.com i wrzucić tam kilka swoich projektów. Następnie opis tych projektów powinieneś dołączyć do swojego CV wraz z linkiem do repozytorium. Szukając pracy jako junior programista ktoś na 90% sprawdzi Twoje programy a to pozwoli mu poznać Twoje umiejętności.

Krok 5. – zbuduj portfolio projektów

Ten punkt jest trochę powiązany z poprzednim, ponieważ aby zbudować portfolio projektów musisz umieć jakiś system kontroli wersji. Nie zastanawiaj się nad żadnym innym oprócz GITa. Jest to sprawa niezwykle ważna, jeżeli nie posiadasz doświadczenia ani wykształcenia informatycznego. W takim wypadku portfolio własnych projektów to absolutny must have, który pozwoli Ci wejść do branży IT.

Nie wyobrażam sobie, na jakiej zasadzie miałoby się odbywać przyjęcie kandydata, który nie ukończył studiów IT i nie ma doświadczenia, do pracy na stanowisku programisty. Własne proste projekty to idealna informacja dla pracodawcy, o tym że:

  • umiesz programować w jakiejś technologii
  • umiesz system kontroli wersji (w pracy będziesz z niego korzystał już od 2 dnia – bo pierwszy dzień to podłączenie komputera)
  • swoje przebranżowienie i zmianę zawodu traktujesz poważnie i podchodzisz do niego profesjonalnie

Sam przed poszukiwaniem pierwszej pracy oczywiście także posiadałem portfolio własnych projektów:

Jednak oprócz projektów bardzo pomógł mi także ten blog, studia IT oraz znajomość wielu technologii (interesowałem się programowaniem od dziecka). Gdy byłem mały, nikt nie doradził mi aby projekty umieścić na githubie i szkoda, że tego nie zrobiłem. Co ciekawe, pytania o GITa i jego podstawową obsługę zostały mi zadane na mojej pierwszej rozmowie o pracę na 2-3 roku studiów.

Krok 6. – przygotowanie teoretyczne do rozmowy

Jeżeli umiesz jakąś technologię w podstawowym zakresie oraz posiadasz kilka małych projektów, to czas na zainteresowanie się teorią. Niestety, rozmowa rekrutacyjna to w większości przypadków przepytanie kandydata z teorii. W 80% przypadków zadawane pytania będą dotyczyć danego języka/technologii, którego programistą jesteś.

Jak przygotować się do tego etapu? Twoje powodzenie zależy oczywiście od stopnia w jakim przyswoiłeś sobie dany język programowania. Pytań na rozmowy rekrutacyjne wyszukuj także w internecie pod hasłami np.: „Java interview questions„. Znajdziesz w ten sposób naprawdę bardzo dużo użytecznych informacji. Te pytania często umieszczane są razem z odpowiedziami. Będzie to dla Ciebie źródło dodatkowej wiedzy i szansa na pewne uporządkowanie niejasności.

Jak rozłożyć w czasie wszystkie powyższe kroki?

Idealnie byłoby gdybyś posiadał kogoś znajomego, kto umie programować i mógłby odpowiadać na wszelkie Twoje niejasności. Z takiej opcji skorzystała moja dziewczyna i wydaje mi się, że jej nauka programowania przyśpieszyła w kosmicznym tempie, gdy zacząłem jej tłumaczyć pewne mechanizmy np. za pomocą historyjek z życia codziennego.

Przyznajmy wprost, nie jestem nauczycielem i nie znam odpowiedniej metodyki nauki programowania. Tylko, czy biorąc pod uwagę szybkość rozwoju tej branży, taka w ogóle istnieje? Wraz z moją dziewczyną przyjęliśmy następujący plan nauki:

Aż do chwili obecnej sprawdza się on idealnie. Mogę powiedzieć, że obecnie znajdujemy się wspólnie już w końcowym etapie. Jako doświadczony programista wydaje mi się, przebranżowienie przebiegło pomyślnie w około 4 miesiące. Czy rzeczywiście tak się stało zweryfikują prawdopodobnie zbliżające się rozmowy rekrutacyjne.

Czy przebranżowienie na programistę jest łatwe?

Moim daniem, odpowiedź brzmi „tak”. Żyjemy w czasach idealnych, aby zostawić swój dotychczasowy zawód i zabrać się za programowanie. Wynika to z małego progu wejścia do tego zawodu, który jest z kolei spowodowany ogromnym deficytem pracowników. Jeżeli chcesz zostać programistą najlepiej zacznij myśleć o tym już teraz. Być może za kilka-kilkadziesiąt lat trend na rynku IT się odwróci, a wtedy dostanie się do branży nie będzie już ani tak łatwe, ani tak opłacalne.

]]>
https://www.p-programowanie.pl/studia-praca/jak-przebranzowic-sie-na-programiste-6-krokow/feed/ 4
Resolver routingu i animacja wczytywania danych https://www.p-programowanie.pl/angular/resolver-routingu-animacja-wczytywania-danych/ https://www.p-programowanie.pl/angular/resolver-routingu-animacja-wczytywania-danych/#comments Thu, 13 Sep 2018 19:23:43 +0000 https://www.p-programowanie.pl/?p=3692 Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
W Angularze istnieje kilka sposobów na wczytanie danych do komponentu. Jednym z mało znanych i rzadziej stosowanych mechanizmów jest tzw. resolver. Tego wyrazu, w moim odczuciu, nie da się przetłumaczyć sensownie na język polski, dlatego taką nazwą będę posługiwać się w całym artykule. Z tego wpisu dowiesz się jakie wady i zalety ma stosowanie resolvera, […]]]>

Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
W Angularze istnieje kilka sposobów na wczytanie danych do komponentu. Jednym z mało znanych i rzadziej stosowanych mechanizmów jest tzw. resolver. Tego wyrazu, w moim odczuciu, nie da się przetłumaczyć sensownie na język polski, dlatego taką nazwą będę posługiwać się w całym artykule. Z tego wpisu dowiesz się jakie wady i zalety ma stosowanie resolvera, jakie są alternatywy, oraz jak w prosty sposób zaimplementować animację wczytywania danych.

Asynchroniczne doczytywanie danych

Aplikacje Angularowe wyposażone są we własny mechanizm routingów, czyli mechanizm wiązania poszczególnych komponentów aplikacji z adresami internetowymi. Podczas załadowania komponentu bardzo często istnieje konieczność doczytania danych pochodzących z zewnętrznych źródeł . Może to być np. profil użytkownika lub słowniki użyte w formularzu.

Aplikacje projektowane w Angularze to tzw. strony SPA (ang. single page application). Cechują się one m.in. dynamicznym doczytywaniem danych na żądanie. Oznacza to, że żądania wysyłane do serwera API są typu XHR (ang. Asynchronous Javascript and XML). Dawniej do obsługi tych żądań należało użyć technologii AJAX. Platforma programistyczna Angular dostarcza programistom gotowe serwisy, które znaczenie upraszczają doczytywanie danych z API. Jest to m.in. serwis $http oraz (od wersji Angulara >=4.3.0) jego nowszy odpowiednik $httpClient. Są one typowymi fasadami nałożonymi na technologię AJAX.

Doczytanie danych do komponentu

Podczas ładowania komponentu powiązanego z danym routingiem mamy dwie opcje doczytania danych. Pierwszą jest użycie resolvera, a drugą bezpośrednie odpytanie serwisu danych np. w konstruktorze lub metodzie OnInit. Użycia serwisów $http lub $httpClient w klasie komponentu w ogóle nie bierzemy pod uwagę, ponieważ warstwa źródła danych powinna być niezależna od logiki biznesowej.

Utworzenie serwisu danych

Niezależnie jaką opcję wybierzesz, niezbędny jest serwis zwracający dane. W większości przypadków będzie on odpytywał backend lub jakiekolwiek inne API o dane, które następnie przekaże do komponentu. Jeżeli rozważamy architekturę Angulara jako wzorzec MVVM (ang. model view view-model) wtedy serwisy należy postrzegać jako warstwę modelu. Powinna ona spełniać następujące cechy:

  • może pobierać dane i nimi manipulować
  • nie może nic wiedzieć o widoku
  • powinna hermetyzować logikę aplikacji (ale już niekoniecznie logikę biznesową)

Dlatego też, serwisy Angularowe powinny pobierać dane, przetwarzać je i zwracać do innych warstw aplikacji bez jakiejkolwiek ingerencji w widok. Na potrzeby tego artykułu utworzę prosty moduł symulujący pobieranie danych z zewnętrznego API:

import { Injectable } from "@angular/core";
import { Observable, of } from "rxjs";
import { delay } from 'rxjs/operators';

@Injectable({
    providedIn: 'root',
})
export class BookService {
    private booksList = [
        { id: 1, name: "Harry Potta" },
        { id: 2, name: "C# secrets" }
    ];

    getAll(): Observable<Book[]> {
        return of(this.booksList).pipe(
            delay(800)
        );
    }
}

Za pomocą operatorów of oraz delay można w wygodny sposób zasymulować zwrócenie statycznych danych w postaci strumienia z pewnych opóźnieniem – zupełnie tak jak zachowuje się odpytywanie zewnętrznego API.

Odpytanie serwisu danych

Najbardziej popularną metodą na wczytanie danych jest odpytanie serwisu zwracającego dane bezpośrednio w konstruktorze danego komponentu. Jest to rozwiązanie proste i często stosowane. Przykładowa implementacja może wyglądać następująco:

import { Book, BookService } from './data.service';
import { Component, OnInit } from '@angular/core';

@Component({
    selector: "app-root",
    templateUrl: "./app.component.html",
})
export class AppComponent implements OnInit {
    private bookList: Book[];
    
    constructor(private bookService: BookService) { }

    ngOnInit(): void {
        this.bookService.getAll().subscribe((result) => {
            this.bookList = result;
        });
    }
}

Kod jest stosunkowo prosty, w konstruktorze klasy wstrzykujemy instancję serwisu, następnie w metodzie ngOnInit ładujemy dane subskrybując się do strumienia danych zwracanego przez metodę getAll().

Warto wspomnieć, że wczytanie danych w konstruktorze komponentu byłoby błędem, ponieważ konstruktor powinien być wykorzystywany tylko do pozyskiwania instancji serwisów z kontenera IoC (ang. inversion of control) Angulara. Nie służy on absolutnie do manipulacji danymi. Wczytanie lub manipulowanie danymi musi odbywać się w cyklu życia aplikacji Angularowej czyli np. w zdarzeniu ngOnInit. Konstruktor nie jest elementem platformy Angular tylko elementem języka TypeScript, nie możemy w nim np. uzyskać uchwytu do danych wejściowych i wyjściowych komponentu (@Input oraz @Output).

Co jest nie tak z powyższym kodem? Metoda getAll() jest asynchroniczna, oznacza to, że zwróci dane w pewnym momencie w przyszłości, jednak na pewno nie w chwili, w której zostanie wykonana. Być może strumień danych zostanie zwrócony za 10ms lub kilka sekund, lecz na pewno wystąpi jakieś opóźnienie.

Ponieważ, nie wiemy kiedy metoda zostanie wykonana, nie możemy w sposób bezpieczny używać zmiennej bookList w szablonie komponentu. Zmienna została zadeklarowana jednak nie została zainicjalizowana. Próba jakiejkolwiek manipulacji na zmiennej w szablonie spowoduje natychmiastowy błąd aplikacji. Jak się przed tym bronić? Mamy trzy wyjścia:

  • zainicjalizowanie zmiennej aby była przykładowo pustą tablicą (sama deklaracja powoduje, że ma ona wartość undefined)
  • otoczenie użycia zmiennej w dyrektywy *ngIf="bookList"
  • użycie operatora bezpiecznej nawigacji ?.

Wszystkie powyższe rozwiązania działają, ale są złe. Poniżej opiszę dlaczego.

Konsekwencje pobierania danych w komponentach

Próba wyświetlenia długości zmiennej lub iterowania po niej spowoduje błąd aplikacji. Wystarczy jedna prosta linijka {{bookList.length}} a konsola zapełni się czerwonymi błędami. Dzieje się tak ponieważ szablon komponentu zostanie wyrenderowany szybciej niż zdąży się wykonać asynchroniczna metoda doczytująca dane.

Co prawda zainicjalizowanie zmiennej pustą tablicą w pełni zabezpieczy aplikację, jednak spowoduje „mryganie” danych w szablonie komponentu. Użytkownikowi przed ułamek sekundy ukaże się napis „0” a następnie „1” symbolizujący długość tablicy. W przypadku próby iterowania po zmiennej efekt będzie jeszcze gorszy, ponieważ na początku strona będzie pusta a po jakimś czasie zostanie wyrenderowana jej treść. W przypadku skomplikowanego szablonu i wolniejszego komputera wszystkie obiekty będą przez chwilę skakać.

Otaczanie całych stron instrukcjami warunkowymi *ngIf jest niewydajne i samo w sobie powinno wywoływać u Ciebie niepokój. Ta dyrektywa nie została stworzona po to, aby obsługiwać nią moment doczytania danych. Poza tym, dalej pozostaje problem mrygającej strony.

Użycie operatora bezpiecznej nawigacji ?. wydaje się najbardziej sensowne. Jego użycie polega na tym, aby odwoływać się nim do wszelkich atrybutów zamiast użycia standardowej kropki. Przykładowo zamiast napisać {{bookList.length}}, co spowoduje wyrzucenie wyjątku gdy zmienna jest niezdefiniowana, można napisać {{bookList?.length}}. Atrybut zostanie doczytany w momencie kiedy tylko będzie to możliwe. Skutkiem używania tego operatora często są konstrukcje w stylu {{person?.attributes?.weight}}. Fajnie, że dodali taki operator, jednak z mojego doświadczenia wynika, że w bardziej zaawansowanych aplikacjach są z nim problemy. Nigdy nie wiemy co i kiedy jest puste, co jest niezdefiniowane. Aplikacja nie zgłasza błędów, a jednak coś gdzieś się nie wyświetla. Na dodatek problem mrygania strony dalej pozostaje nierozwiązany.

Na szczęście, są rozwiązania znacznie bardziej eleganckie.

Resolver jako jedyne słuszne rozwiązanie

Resolver jest mechanizmem umożliwiającym doczytanie danych do komponentu, zanim ten zostanie załadowany. Można w nim wykonać kod asynchroniczny lub zwrócić dane statyczne. W celu utworzenie resolvera wystarczy utworzyć klasę, zaimplementować generyczny interfejs Resolve<T> oraz udekorować ją dekoratorem @Injectable(). Aby resolver działał należy połączyć go z danym komponentem w definicjach routingu.

W poprzednim akapicie opisałem wiele problemów związanych z doczytywaniem danych. Jeżeli te argumenty Cię nie przekonały przeanalizujemy zalety jakie niesie ze sobą używanie resolverów. Przede wszystkim tworzą one dodatkową, abstrakcyjną warstwę pomiędzy komponentem a routingiem. Dzięki nim, logika odpowiedzialna za dostarczenie danych nie znajduje się bezpośrednio ani w definicji routingu, ani w definicji komponentu. Co za tym idzie?:

  • komponent jest bardziej bezstanowy, być może w innym miejscu aplikacji re-użyjesz go i prześlesz dane w inny sposób (np. za pomocą @Input). Bez resolvera – obarczając komponent logiką ładowania danych – będziesz musiał używać instrukcji warunkowych aby tę logikę ominąć.
  • do każdego komponentu może być podpięta dowolna ilość resolverów, dopóki wszystkie nie zostaną rozwiązane komponent nie załaduje się. Dzięki temu nie trzeba otaczać ciała komponentu instrukcjami warunkowymi ani używać operatora bezpiecznej nawigacji.
  • resolvery można reużywać w wielu innych komponentach i jest to zabieg bardzo często stosowany. Jeżeli doczytujesz jakieś słowniki w komponencie tworzenia dokumentu, prawdopodobnie będziesz ich potrzebował podczas wyświetlania i edycji.
  • pozwalają zaimplementować animację wczytywania danych w bardzo prosty sposób (np. animowany spinner lub loading-bar) – jest to nieodłączony element każdej strony SPA.
  • jeżeli chcesz pokryć aplikację testami, łatwiej napisać test do resolvera i do komponentu osobno, niż do klasy scyzoryka-szwajcarskiego, która wczytuje i przetwarza dane

W prosty sposób można utworzyć resolver obsługujący serwis utworzony w poprzednim akapicie:

import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from "@angular/router";
import { Observable } from "rxjs";
import { BookService } from './data.service';

@Injectable({
    providedIn: 'root',
})
export class BooksResolver implements Resolve<Book[]> {
    constructor(private bookService: BookService) { }

    resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Book[]> {
        return this.bookService.getAll();
    }
}

Budowa resolvera jest bardzo prosta. Składa się z jednej metody zwracającej dane, które zostaną później przekazane do komponentu. Dane statyczne zostają przekazane natychmiast, dane asynchroniczne (obserwator) zostają najpierw rozwiązane a później przekazane do komponentu.

W powyższym przypadku zwracany jest obserwator, jednak dzięki operatorowi unii w deklaracji interfejsu, metoda może zwrócić jedną z trzech wartości: Observable<T> | Promise<T> | T.

Związanie resolvera z komponentem

Związanie resolvera z komponentem odbywa się poprzez routing zdefiniowany w RouterModule. Przykładowe związanie może wyglądać nastęująco:

imports: [
    BrowserModule,
    RouterModule.forRoot([
        {
            path: 'books',
            component: BooksList,
            resolve: {
                books: BooksResolver
            }
        }
    ])
],

Jest to fragment kodu wyciągnięty z głównego modułu aplikacji (app.module.ts). Mówi on, że dla routingu /books powinien zostać załadowany komponent BooksListoraz powinien zadziałać resolver BooksResolver. Dane z resolvera będą dostępne pod atrybutem books.

Jak odebrać dane z resolvera? Należy użyć ActivatedRoute:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Book } from './data.service';

@Component({
    selector: "books-list-component",
    templateUrl: "./books-list.component.html",
})
export class BooksList implements OnInit {
    private booksList: Book[];

    constructor(private activatedRoute: ActivatedRoute) { }

    ngOnInit(): void {
        this.booksList = this.activatedRoute.snapshot.data.books;
    }
}

W tym momencie lista książek jest wypełniona wartościami zwróconymi przez serwis. Komponent nie uruchomi się do czasu rozwiązania wszystkich resolverów. Wszystkie resolvery wykonują się jednocześnie (niesekwencyjnie), nie istnieje żadna kolejność.

Doczytywanie danych w ten sposób, pozwala w bardzo prosty sposób wdrożyć animację wczytywania danych charakterystyczną dla stron SPA.

Animacja wczytywania danych

Jeżeli korzystasz w aplikacji z resolverów, w bardzo prosty sposób możesz zaimplementować serwis odpowiedzialny za animację wczytywania danych. Założenia są bardzo proste:

  • animacja to zwykły komponent (np. spinner.component.ts)  wyświetlany na całej szerokości ekranu (przykrywający całą zawartość strony). Można to łatwo osiągnąć za pomocą CSS
  • aby ręcznie nie sterować zachowaniem komponentu ani nie powielać jego występowania najlepiej uzależnić go od dodatkowego serwisu (np. spinner.service.ts)
  • aby zautomatyzować pokazywanie/ukrywanie komponentu ładowania należy wpiąć się w zdarzenia emitowane przez Router

Zacznijmy od serwisu. Serwis sterujący animacją musi mieć dostęp do serwisu Router – więc wstrzykujemy do w konstruktorze. Następnie należy zasubskrybować się do jego zdarzeń i względem nich sterować odpowiednią flagą. Zdarzenia, które nas interesują to:

  • NavigationStart – w tym momencie pokazujemy animację wczytywania
  • NavigationEndNavigationCancelNavigationError – w tym momencie ukrywamy animację wczytywania

Pojawia się ostatnie pytanie: jak powiadomić komponenty z poza serwisu, że zmienia się w nim wartość flagi (np. isLoading)? W czystym JavaScripcie musielibyśmy użyć funkcji zwrotnej (ang. callback). W Angularze oczywiście lepiej skorzystać z dobrodziejstw RxJs oraz klasy Subject. Subject tak jak EventEmitter pozwala emitować wartości do wszystkich nasłuchujących subskrybentów. Przykładowy kod:

import { Injectable } from "@angular/core";
import { NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router';
import { Event } from '@angular/router/src/events';
import { BehaviorSubject, Observable } from "rxjs";

@Injectable()
export class SpinnerService {

    private _isLoading$ = new BehaviorSubject<boolean>(false);

    get isLoading() {
        return this._isLoading$.asObservable();
    }

    set loading(value: boolean) {
        this._isLoading$.next(value);
    }

    constructor(private router: Router) {
        this.eventHandler(this.router.events);
    }

    private eventHandler(events: Observable<Event>) {
        events.subscribe((event) => {
            if (event instanceof NavigationStart) {
                this._isLoading$.next(true);
            } else if (
                event instanceof NavigationEnd ||
                event instanceof NavigationCancel ||
                event instanceof NavigationError
            ) {
                this._isLoading$.next(false);
            }
        });
    }

}

Przeanalizujmy powyższy kod. Po pierwszy zamiast klasy Subject użyłem klasy BehaviourSubject. Niczym się one nie różnią oprócz tego, że ten drugi można zainicjować domyślną wartością. Po drugie Subject i wszelkie jego odmiany muszą być prywatne, aby ktoś z zewnątrz nie mógł ich zepsuć, zresetować i obsłużyć w zły sposób. Z tego powodu wartość Subjecta pobieramy za pomocą właściwości i zwracamy jako obserwator.

W konstruktorze serwisu przesyłamy zdarzenia Routera do funkcji eventHandler. Logika obsługi zdarzeń powinna być wydelegowana do innej metody a nie upchana w konstruktorze. Ostatnią kwestią jest dodanie settera umożliwiającego ręcznie sterować stanem animacji ładowania. Zapytasz po co? Mechanizm jest dzięki temu bardziej reużywalny. Oprócz automatycznego działania z resolverami można go uruchamiać ręcznie np. w metodzie komponentu przy asynchronicznych operacjach (np. wykonanie zapytania z zapisem danych do back-endu).

Obsłużenie animacji jest bardzo proste, wystarczy utworzyć dowolny komponent, wystylizować go odpowiednie za pomocą CSS, a następnie sterować jego widocznością w zależności od stanu serwisu SpinnerService.

<div class="spinner" [ngClass]="{'hidden': !(spinnerService.isLoading | async)}">
    <i class="fas fa-spin fa-spinner"></i>
</div>

To właściwie tyle jeżeli chodzi o automatyczną animację wczytywania danych. Aby całość działała należy jeszcze tylko umieścić komponent animacji gdzieś w głównym pliku szablonu naszej aplikacji <spinner></spinner>. Serwis zostanie do niego wstrzyknięty automatycznie, na komponent odpowiedzialny za animację zostanie automatycznie nakładana klasa .hidden w zależności od stanu serwisu. Stan będzie zmieniał się automatycznie w zależności od zdarzeń Routera lub manualnej ustawienia settera przez programistę.

Przykładowe działanie mechanizmu:

]]>
https://www.p-programowanie.pl/angular/resolver-routingu-animacja-wczytywania-danych/feed/ 1
Rozmowa rekrutacyjna programisty https://www.p-programowanie.pl/studia-praca/rozmowa-rekrutacyjna-programisty/ https://www.p-programowanie.pl/studia-praca/rozmowa-rekrutacyjna-programisty/#comments Fri, 29 Jun 2018 15:03:47 +0000 https://www.p-programowanie.pl/?p=3647 Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Programiści często zmieniają pracę i często uczestniczą w rozmowach rekrutacyjnych. Jest to spowodowane dużą rotacją w branży IT, co z kolei jest wynikiem deficytu pracowników na rynku pracy. Rekrutacje bywają stresujące, wymagają gruntownego odświeżenia wiedzy z zakresu programowania oraz zajmują dużo czasu. W tym artykule opiszę jak wygląda rozmowa rekrutacyjna programisty i na jakie elementy […]]]>

Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Programiści często zmieniają pracę i często uczestniczą w rozmowach rekrutacyjnych. Jest to spowodowane dużą rotacją w branży IT, co z kolei jest wynikiem deficytu pracowników na rynku pracy. Rekrutacje bywają stresujące, wymagają gruntownego odświeżenia wiedzy z zakresu programowania oraz zajmują dużo czasu. W tym artykule opiszę jak wygląda rozmowa rekrutacyjna programisty i na jakie elementy trzeba – moim zdaniem – uważać.

Rozmowa techniczna

Głównym etapem rozmowy rekrutacyjnej na stanowisko programisty jest rozmowa techniczna. Polega ona na tym, aby odpowiadać na pytania zadawane przez osobę techniczną, która nas rekrutuje. Przeważnie jest to jakiś starszy programista lub starszy projektant systemowy. Jego zadanie jest proste – sprawdzić czy wiedza na temat technologii wpisanych w CV jest wystarczająca na dane stanowisko. Ten etap jest kluczowy i praktycznie decyduje o zatrudnieniu bądź jego braku.

Podczas rozmowy technicznej mogą zostać poruszone następujące kwestie:

  • omówienie dotychczasowych projektów (od strony technicznej)
  • pytania o języki programowania i platformy programistyczne wpisane w CV
  • pytania o wzorce projektowe, dobre praktyki pisania kodu
  • pytania o najbardziej znane algorytmy i struktury danych
  • pytania o sposób rozwiązania jakiegoś problemu (od strony technicznej)

To, które pytania zostaną zadane oczywiście zależy od profilu stanowiska na jakie aplikujesz. Programiści niskopoziomowi usłyszą więcej pytań o algorytmach, backendowcy o wzorcach projektowych, a tzw. programiści full-stack często dostają pytania architektoniczne z bardzo szerokiego przekroju technologii (czyli jak przy użyciu kilku technologii na raz osiągnąć daną rzecz).

Pytanie o dotychczasowe projekty

Jest to bardzo ważne pytanie, z czego wiele osób nie zdaje sobie sprawy. Absolutnie nie jest to pytanie na rozgrzewkę, aby pogadać o czymś „luźnym”. Bo przecież wydawać by się mogło, że ktoś nie może Cię zagiąć pytaniami o projekt, o istnieniu którego nie ma pojęcia? – nic bardziej mylnego!

Techniczna osoba rekrutująca często pyta o projekty, aby wybadać jak mocno kandydat zaangażowany był w dany projekt. Jeśli okaże się, że mało wiesz o projekcie w którym pracowałeś, to działa to bardzo na Twoją niekorzyść. Zostaniesz automatycznie sklasyfikowany jako klepacz kodu, który nie dostawał żadnych ważnych zadań.

Jeśli natomiast od podszewki wytłumaczysz całą architekturę projektu, najważniejsze mechanizmy w nim występujące, jak rozwiązane były pewne problematyczne kwestie przewijające się w większości dużych projektów – sprawdzisz dobre wrażenie. Będzie to znak, że byłeś w tym projekcie kimś znaczącym, mającym realną wiedzę i wpływ na jego wygląd. Będzie to znak, że pełniłeś rolę lidera zespołu (formalnego czy też nie). Każdy chce mieć w drużynie taką osobę, w odróżnieniu od zwykłego klepacza kodu od „prostych” zadań. A musisz się ze mną zgodzić, że w każdym projekcie występuje kilka osób silnych i kilka osób naturalnie słabszych.

To czy byłeś słaby czy dobry, albo lepiej czy byłeś kimś znaczącym czy tez nie, wyjdzie właśnie podczas tego pytania. Oczywiście nie ma nic złego w osobach dostających prostsze zadania (np. stażyści lub juniorzy). Każdy kiedyś zdobędzie większe doświadczenie ale wszystko wymaga odpowiedniej ilości czasu. Jednak bez względu na to jaką pozycję zajmowałeś w dotychczasowym projekcie, idąc do nowej firmy lepiej zaprezentować się jako ktoś mocny.

Rola osoby technicznej

Należy zdać sobie sprawę, że osoba techniczna ma największy wpływ na to, czy zostaniesz zatrudniony. Dlatego tak ważne jest aby umieć opowiadać o poprzednich projektach, a także aby dobrze wypaść na reszcie pytań technicznych.

Czy wiesz, dlaczego tak jest? Osoba techniczna na rozmowie to bardzo często lider zespołu, do którego jesteś wstępnie rekrutowany. To ten lider decyduje czy chciałby kogoś takiego jak Ty w swoim zespole. Najlepiej jeśli na rozmowie technicznej wypadniesz po prostu dobrze i najlepiej abyś złapał z osobą techniczną wspólne fale – to prawdopodobnie z nią będziesz później pracował.

Z rekruterem sprawdzającym umiejętności miękkie, języki obce itd. (czyli tzw. pracownik HR) nie musisz złapać żadnej szczególnej więzi. Prawdopodobnie więcej go w życiu nie zobaczysz. W zależności od polityki firmy jego opinia raczej nie jest kluczowa jeśli chodzi o zatrudnienie, jednak o tym będzie w kolejnych akapitach.

Osoba techniczna na rozmowie

Warto wspomnieć więcej i skupić się na osobie technicznej. Tak jak zostało napisane w poprzednim akapicie, osoba techniczna to przeważnie lider zespołu, do którego zostaniesz przyjęty. Tak po prostu musi być. Zawsze musi pojawić się osoba z danego projektu, aby mogła stwierdzić czy będziesz pasował do danego zespołu mentalnie a także pod względem umiejętności technicznych.

Zasada ta spełniła się podczas wszystkich kilkunastu rozmów rekrutacyjnych, w których brałem udział. Daje Ci to bardzo ważnego asa w rękawie. Rozmowa rekrutacyjna jest po to aby firma mogła sprawdzić Ciebie, a także abyś Ty mógł sprawdzić firmę. Zwracaj szczególną uwagę na osobę techniczną i zadaj sobie cały czas pytanie: „Czy chciałbym z tym gościem pracować?” – bo prawdopodobnie będziesz musiał.

Nieodpowiednia osoba techniczna

Za tytułem tego akapitu kryje się przestroga dla Ciebie.  Czasem wydaje mi się, że firmy informatyczne nie mają pojęcia jak kluczowa jest rola osoby technicznej, którą wysyłają na rekrutację.

Niestety zdarza się, że pewne osoby zajmują stanowiska, których nie powinny zajmować. Czasem zespołem zarządza osoba, która nie powinna. Czasem najważniejsze zdanie należy do osoby, która nie ma największych umiejętności technicznych w danym projekcie. Jest to bardzo negatywne dla firmy a dla Ciebie podczas rozmowy rekrutacyjnej powinno być sygnałem, aby nie zaczynać z daną firmą współpracy.

Przypadek 1: Osoba techniczna ma mniejszą wiedzę niż Ty

Będąc na rozmowie rekrutacyjnej warto pytać i dowiadywać się maksymalnie dużo. Warto wykorzystać szansę na rozmowę jaką dostaliśmy. Osobiście zawsze staram się jak najwięcej pogadać z osobą techniczną rekrutującą mnie, ponieważ pozawala mi to uzyskać obraz teamu do jakiego zostanę w przyszłości przydzielony.

Aby czegoś się dowiedzieć wystarczy starać się odbijać piłeczkę po każdym pytaniu technicznym. Przykładowo jeżeli dostaniesz pytanie o dany wzorzec projektowy, postaraj się go dogłębnie opisać, dać przykład użycia, opowiedzieć po co się go stosuje i od razu zapytaj „czy też w projekcie mieli taki problem?”.

Takie wywiązywanie dyskusji nie jest złe. To nie cwaniactwo. To szansa dla Ciebie na poznanie swojego przyszłego lidera. Każda merytoryczna dyskusja osób technicznych to coś mega pozytywnego, szansa na podzielenie się wiedzą. Po kilku pytaniach oboje znacie swój potencjał techniczny i oboje wiecie z kim macie do czynienia – z kim przyjdzie wam współpracować.

Jeżeli zdarzy się, że osoba rekrutująca zadaje Ci pytania, a Twoje wypowiedzi są tak rozbudowane, że osoba techniczna nie umie podjąć z Tobą żadnej dyskusji – powinien być to dla Ciebie sygnał ostrzegawczy. Możesz wtedy zakładać, że zatrudnienia w tej firmie szybko zaczniesz żałować.

Przypadek 2: Osoba techniczna zadaje niemądre pytania

Poniekąd ten akapit wiąże się z poprzednim, jednak problem jest poważny i warty opisania. Wyobraź sobie, że idziesz na rozmowę rekrutacyjną i strzelasz w stanowisko regular/senior. Masz sporą wiedzę, niezły bagaż doświadczenia, rzucasz duże oczekiwania finansowe. Następnie siadasz wygodnie w fotelu słyszysz pierwsze pytanie: „czym różni się klasa od interfejsu?”. Pojawia się tysiąc myśli na sekundę.

Czy jest to pytanie, które należy zadać osobie rekrutującej się za dużą stawkę na takie stanowisko? Gdy pada takie pytanie zawsze myślę sobie w głowie, że albo trafiłem na rozmowę do złej firmy, albo oni znaleźli złego kandydata. Pytania trzeba dostosować do stanowiska i doświadczenia osoby, którą się rekrutuje. Rekrutując się na regulara/seniora nie można dostawać pytań z technikum – bo to źle wróży o firmie.

Rekrutując się na poważne stanowisko do poważnej firmy, chciałbym aby ta firma widziała we mnie potencjał i eksperta w danej dziedzinie. Chciałbym współpracować z wykwalifikowanymi ludźmi. Rekrutując się na stanowisko seniora chciałbym usłyszeć pytanie „czym różni się single dispatching od double dispatching” a nie „czym różnie się pętla for od while” lub „czym różni się klasa od interfejsu”. Takie pytania są dla stażystów lub osób bez doświadczenia, jeśli po 5 latach pracy ktoś Ci je zada na rozmowie – uciekaj.

Rozmowy rekrutacyjnej nie można traktować jak sprawdzianu w szkole, który chciałbym szczęściem zaliczyć i dostać się do danej firmy. Dlatego nie warto cieszyć się, że „siadły łatwe pytania”. Jeśli firma nie umie przeprowadzić odpowiedniej, profesjonalnej rekrutacji, możesz przypuszczać, że reszta teamu z którym będziesz współpracować dostała się do tej firmy właśnie po takich pytaniach jak to, które usłyszałeś – czyli będzie problem.

W takiej sytuacji można także przypuszczać, że firma szuka kogoś innego niż Ty, prawdopodobnie kogoś z mniejszym doświadczeniem. Już na samym początku zostajesz przez firmę niedoceniany. Niech w takich wypadkach nie zwiedzie Cię stawka, ponieważ tajemnicą nie jest, że w dużych korporacjach zdarza się, że ktoś zarabia dużo a niewiele robi.

Przypadek 3: Szybka, prosta rekrutacja

Ten akapit, znowu, wiąże się z poprzednim. Rekrutacja na stanowisko programisty, moim zdaniem, nie powinna być zbyt krótka ani prosta. W 30 minut nie da się sprawdzić potencjału kandydata. Osobiście mam bardzo dobrą opinię o firmach, które przeprowadzają wieloetapowe rekrutacje. Moim zdaniem zespoły programistyczne w takich firmach są po prostu lepiej organizowane.

Jako programista mam świadomość swoich silnych i słabych stron. Moją słabą stroną są bazy danych i bardzo cenię sobie współpracę z ludźmi, którzy z baz danych są ponadprzeciętni. Dlaczego? Ponieważ z taką osobą idealnie się uzupełniam. Jeżeli dana firma zatrudnia full-stack developera, a przepytuje go tylko z back-endu podczas jednego etapu rekrutacji, wtedy bardzo możliwe, że zespół do którego trafisz będzie źle dobrany. Może się okazać, że np. większość zespołu to bardzo dobrzy back-endowcy, jednak nie ma nikogo kto mógłby architektonicznie ogarnąć front-end. Z czego to wynika? Oczywiście ze złej, zbyt szybkiej rekrutacji.

Idąc na rozmowę rekrutacyjną nie wiesz jaki jest potencjał Twojego przyszłego zespołu i jakich kompetencji temu zespołowi brakuje. To rolą firmy rekrutującej jest przeprowadzenie profesjonalnej i skutecznej weryfikacji Twoich zdolności. Na tych pojedynczych rekrutacjach poszczególnych osób budowane są całe zespoły stanowiące o sukcesie lub porażce.

Prośba o rozwiązanie zadania programistycznego

Bardzo często zdarza się, że firma prosi o rozwiązanie zadania rekrutacyjnego w ramach pierwszego etapu rekrutacji. Oj, na ten temat można teraz pisać dużo.. Wiele razy sam naciąłem się na takie zadania, wielu moich znajomych także. Według mnie zasada jest prosta:

  • jeśli jest to zadanie 30 minutowe (udostępnione np. na platformie Codility) spokojnie możesz je rozwiązać. Mam z takimi zadaniami pozytywne doświadczenia (szybko, sprawnie).
  • jeżeli jest to zadanie tekstowe podesłane w formie PDF, którego wykonanie zajmie Ci kilka dni – odpuść.

30 minutowe zadania na Codility rzeczywiście potrafią pokazać, czy masz jakiekolwiek pojęcie o programowaniu. Przeważnie mają charakter algorytmiczny, polegają na napisaniu jednej prostej funkcji pobierającej dane i zwracającej je w jakimś formacie. Dużo z takiego zadania wyciągnąć się nie da na temat kandydata, ale firma ma jakiś punkt zaczepienia, wie czy warto w ogóle zapraszać Cię na dalsze etapy.

Zadania dłuższe (opisane i podesłane w formacie osobnego pliku PDF) przeważnie zajmują więcej czasu, są bardziej skomplikowane. Firma przykładowo może domagać się abyś napisał jakąś webową przeglądarkę zdjęć, prosty webowy system z autoryzacją i formularzami (wymagane podpięcie do bazy danych czy też użycie ORMa). Zmarnujesz na takie zadanie bardzo dużo czasu a prawdopodobnie i tak ktoś przyczepi się do Twojego rozwiązania. Pomyśl sobie teraz, że rekrutujesz się do kilku firm na raz, gdyby każda z firm wymagała od Ciebie takiego zadania spokojnie możesz sobie zarezerwować tydzień życia na coś tak bezsensownego.

Dlaczego tak jest? Firmy myślą, że za pomocą takiego zadania poznają Twój styl kodowania, sprawdzą czy Twój kod jest czysty. Jest to totalna bzdura. Te zadania są czasochłonne, może Ci je pomóc pisać kilku znajomych na raz. Dodatkowo nie istnieje nic takiego jak jeden wybrany, prawilny, obowiązujący styl kodowania. Styl kodowania zależy od projektu do jakiego wchodzisz a przede wszystkim od technologii. Dotyczy to także zasad czystego kodu. Rekrutując się do nowej firmy musisz pisać projekt tak, jak pisali go Twoi poprzednicy.

Każdy .NETowiec pisze kod inaczej niż Javowiec. Jeśli ktoś pisze dużo w TypeScript totalnie nie spodoba się to JavaScriptowcowi. W każdym języku wszystko jest inne, wszystko ma inną konwencję nazywania klas, zmiennych, interfejsów, właściwości.

Przykład z życia wzięty

Kiedyś poświęciłem 4 godziny swojego cennego czasu aby rozwiązać zadanie rekrutacyjne. W języku C# napisałem klasę do parsowania zakresów dat (w bardzo dużym uproszczeniu). Oprócz zakresów dat miałem obsługiwać wszystkie możliwe opcje parsowania, strefy czasowe, formaty wprowadzania dat. Kod był moim zdaniem idealny obiektowo, kierowałem się zasadami SOLID, wszelkie zmienne były zahermetyzowane wewnątrz klasy. Pochwaliłem się nawet koledze w pracy – oboje stwierdziliśmy, że idealniej się już nie da.

Klasa, którą napisałem posiadała wiele prywatnych zmiennych oraz jedną publiczną właściwość. Publiczna właściwość była wypełniana wartością domyślną jednak pozwalała programiście zmienić sposób działania klasy (parsowania dat). Stworzyłem tę właściwość jako publiczną celowo, bo uznałem, że ten parser daty powinien taką mieć. Uznałem, że powinno się móc zmienić sposób jego działania z poza obrębu klasy.

Po kilku dniach otrzymałem odpowiedź: „Wszystkie zmienne w klasie powinny być prywatne, a jedna jest u Pana publiczna. To są złe praktyki. Jednak ponieważ rozwiązanie nam się podoba proszę poprawić kod i przysłać ponownie do oceny”.

Gdy przeczytałem tę wiadomość ręce mi opadły. Podpisał się pod nią „senior architect”. Sprawdzając mój kod, prawdopodobnie, nie odróżnił publicznej właściwości od publicznej zmiennej. Już miałem napisać, że prywatna zmienna istnieje, tylko zostanie niejawnie utworzona przez kompilator, a uproszczona składania tworzenia właściwości w C# de facto zostanie rozwinięta do dwóch metod set i get. Utworzenie publicznego settera nie ma przecież nic wspólnego z utworzeniem publicznej zmiennej.. Stwierdziłem jednak, że nie ma sensu. Po zmarnowaniu kilku godzin na pisanie zadania stwierdziłem, że ocenia je osoba niekompetentna, która skutecznie zniechęciła mnie do dalszej rekrutacji. Grzecznie podziękowałem i odpuściłem rekrutację.

Umiejętności miękkie

Szczerze mówiąc, z mojego doświadczenia wynika, że mało firm zwraca uwagę na tzw. umiejętności miękkie. Wynika to z prostego faktu: programista jest od pisania dobrego kodu, czasem przeprowadza też analizy. Programista nierzadko uczestniczy w rozmowach z klientem jednak docelowo od programisty nie wymaga się nienagannej aparycji i prezentacji – bo jest tylko programistą. Sprzedać produkt i opowiadać o nim muszą osoby zajmujące inne stanowiska.

Artykuły i rady mówiące o tym jak ważne są umiejętności miękkie w naszym zawodzie, nie znajdują odzwierciedlenia w przypadku moich doświadczeń. U programistów przede wszystkim oceniana jest wiedza merytoryczna i doświadczenie. Jest to główny czynnik przeważający o sukcesie zatrudnienia programisty bądź nie.

Podsumowanie

Nie jest lekko, rozmowy rekrutacyjne nie należą do najłatwiejszych. Gdy trafimy na „zbyt trudną”, to oczywiście nie dobrze, jednak często istnieje możliwość renegocjacji stawki jeśli nasza wiedza jest zbyt mała. Jeśli rozmowa jest „zbyt łatwa” uważaj na jaką firmę trafiłeś. Może okazać się, że nie będziesz z niej zadowolony. Szczególnie jeśli Twoja zmiana pracy podyktowana jest chęcią zdobywania doświadczenia a nie „wpłynięcia do spokojnego portu” i zarabiania pieniędzy.

Idąc na rozmowę rekrutacyjną miej w głowie kilka rad z tego artykułu. Idź na nią myśląc, że chcesz (dla siebie) wynieść jak najwięcej informacji o danej firmie, a nie z przeświadczeniem aby „pytania, które się pojawią nie były zbyt trudne”. Jeśli tego nie zrobisz, być może spędzisz kilka lat w firmie, w której naprawdę nie chcesz pracować – tylko w danej chwili jeszcze tego nie wiedziałeś.

]]>
https://www.p-programowanie.pl/studia-praca/rozmowa-rekrutacyjna-programisty/feed/ 7
Jak pozbyć się singletonów w Angularze? https://www.p-programowanie.pl/angular/jak-pozbyc-sie-singletonow-w-angularze/ https://www.p-programowanie.pl/angular/jak-pozbyc-sie-singletonow-w-angularze/#comments Tue, 27 Mar 2018 23:46:09 +0000 https://www.p-programowanie.pl/?p=3600 Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Praktycznie każdy serwis w Angularze jest domyślnie singletonem. Od tej reguły istnieją pewne wyjątki jednak tylko w ściśle określonych przypadkach. Niestety, nie zawsze chcemy aby serwis, którego używamy, zachowywał się jak singleton. W moim przekonaniu Angular nie dostarcza prostego, intuicyjnego mechanizmu aby programista mógł wybrać, jakiego rodzaju serwisu potrzebuje. W tym artykule podzielę się z […]]]>

Warning: Illegal string offset 'feed' in /home/pprogram/public_html/p-programowanie/wp-content/plugins/contextual-related-posts/includes/content.php on line 179
Praktycznie każdy serwis w Angularze jest domyślnie singletonem. Od tej reguły istnieją pewne wyjątki jednak tylko w ściśle określonych przypadkach. Niestety, nie zawsze chcemy aby serwis, którego używamy, zachowywał się jak singleton. W moim przekonaniu Angular nie dostarcza prostego, intuicyjnego mechanizmu aby programista mógł wybrać, jakiego rodzaju serwisu potrzebuje. W tym artykule podzielę się z Tobą kilkoma sposobami, których używam aby osiągnąć cel – czyli instancyjność serwisów.

 Wstrzykiwanie zależności w Angularze

Angular jest wyposażony we własny mechanizm wstrzykiwania zależności i chwała mu za to. Jest to jedna z cech tego frameworka, za którą go pokochałem. Programując przykładowo w ASP.NET MVC bądź Node.JS programista sam musi zadbać o jakiś kontener IoC (ang. inversion of control). W .NET bardzo prosty i popularny jest Ninject, w JS można użyć przykładowo InversifyJS. Użyć, czyli w tym wypadku dodać do projektu, i odpowiednie skonfigurować. W bardziej zaawansowanych przypadkach może to sprawić problemy początkującym programistom, szczególnie, jeśli chcemy do mechanizmu DI (ang. dependency injection) dodać obsługę połączenia z bazą danych (np. wzorzec Unit of work). Pojawiają się wtedy problemy z transakcjami, zakresami transakcji, pulą połączeń itd.

Całe szczęście, zarówno AngularJS jak i Angular2+ załatwił sprawę z góry. Wszystkie serwisy są wstrzykiwalne poprzez wbudowany mechanizm DI. W AngularJS każdy serwis był singletonem i nie było na to lekarstwa. Wynika to z faktu, że posiadał on jeden globalny injector.

W Angular2+ może występować wiele injectorów a cały mechanizm DI jest hierarchiczny. DI w Angular2+ to naprawdę rozbudowany temat i na pewno napiszę o tym osobny artykuł. Z tego względu pominę wiele technicznych szczegółów i opiszę tylko jaki jest czas życia serwisów.

Wstrzykiwalne serwisy

Aby serwis był wstrzykiwalny należy poprzedzić jego definicję dekoratorem @Injectable. Użycie tego dekratora jest sygnałem dla mechanizmu DI, że ma brać ten serwis pod uwagę. Drugim i ostatnim krokiem, jest dodanie serwisu do tablicy providers w module, w którym się znajdujemy. To wszystko. Zdefiniowaliśmy wstrzykiwalny serwis.

Aby pobrać instancję serwisu można użyć następujących sposobów:

// domyślne
constructor(private customService: CustomService) {
	this.customService.testMethod();
}

// poprzez dekorator @Inject
constructor(@Inject(CustomService) private customService) {
	this.customService.testMethod();
}

// poprzez instancję injector'a
constructor(private injector: Injector) {
	this.customService = Injector.get(CustomService);
}

W większości przypadków korzysta się z podejścia domyślnego. Czasem aby pobrać uchwyty do niestandardowych obiektów, które nie są serwisami (np. uchwyt OpaqueToken lub InjectionToken) trzeba użyć dekoratora. Czasem natomiast aby zapobiec błędowi cicrular dependency lub cyclic dependency (szczególnie podczas korzystania z HTTP_INTERCEPTORHttpClient) trzeba skorzystać z instancji injectora. Nie jest to jednak w tym artykule ważne.

Wszystkie wstrzyknięte w ten sposób (każda z powyższych metod) serwisy, są singletonami.

Jeden injector dla wielu modułów

Komponenty i dyrektywy są zahermetyzowane wewnątrz modułów, w których zostały zadeklarowane, jednak nie dotyczy to serwisów. Wynika to z faktu, iż podczas fazy kompilowania wszystkie moduły Angulara są scalane w jeden główny moduł, a wszystkie serwisy lądują w jednej, wspólnej tablicy dostępnych serwisów. M.in. dlatego serwisów nie trzeba eksportować (w odróżnieniu do komponentów i dyrektyw) reużywając je pomiędzy modułami. Przez to nie jest też ważne, czy serwis użyjemy w tym samym module, w którym go zdefiniowaliśmy, czy w całkiem innym module aplikacji. Serwis zawsze będzie dostępny wszędzie i będzie singletonem.

Dzieje się tak ponieważ, wszystkie scalone moduły Angulara podczas fazy kompilacji dostają jeden wspólny injector. Wiążą się z tym kolejne konsekwencje np. możliwość nadpisywania (przesłaniania) serwisów pomiędzy modułami, jednak to nie temat na ten wpis.

Kiedy próbujemy uzyskać uchwyt do serwisu poprzez konstruktor, odpytujemy injector o uchwyt do interesującego na serwisu. Jeśli zrobimy to pierwszy raz, zostanie niejawnie utworzona jego instancja (leniwie), a za każdym kolejnym razem zostanie zwrócona ta sama, poprzednio utworzona instancja. Pojawia się więcej pytań niż odpowiedzi, który injector odpytujemy?

Hierarchiczne wstrzykiwanie zależności

W Angularze DI jest hierarchiczne, a tak naprawdę hierarchiczne są injectory. Podobny mechanizm zapewnia biblioteka InversifyJS, której można użyć w np. w NodeJS. Jeśli posiadamy kilka zdefiniowanych modułów, podczas kompilacji są one scalone w jeden i wyposażone w jeden wspólny injector. Konstruktor każdego komponentu i serwisu szuka tego injectora hierarchicznie.

Wyobraźmy sobie strukturę jak na powyższym obrazku. Serwis dostarczony w głównym module aplikacji będzie singletonem w każdym komponencie w aplikacji, bo cała aplikacja posiada wspólny injector. Użycie serwisu CustomService w każdym komponencie będzie oznaczało operowanie na tej samej instancji.

Jedynym sposobem na pozbycie się singletona jest utworzenie nowego injectora. Można tego dokonać na następujące sposoby:

  • moduł, w którym deklarujemy serwis, doczytać leniwie (ang. lazy loading)
  • dodać tablicę providers w dekoratorze komponentu,w którym chcemy utworzyć nowy injector (przełamać łańcuch DI). Następnie dodać do tablicy interesujący nas serwis.
  • utworzyć serwis ręcznie operatorem new (bez korzystania z DI Angulara, bez dekoratora @Injectable)
  • tworzyć ciągle nowe serwisy rozszerzające (ang. extends) bazowy serwis (dziedzicząc jego logikę i funkcje)

 

W poniższych akapitach opisze własne doświadczenia i przemyślenia na temat tworzenia nowych injectorów.

Leniwie doczytany moduł

Angular udostępnia mechanizm leniwego doczytywania modułów poprzez odpowiednią konstrukcję routingów. Jakie niesie to ze sobą wady i zalety nie naddaje się do opisania w tym artykule. Ważne jest natomiast to, że każdy doczytany leniwie moduł posiada osobny injector. Jest to zrozumiałe, ponieważ w momencie kompilacji aplikacji Angularowej leniwie doczytany moduł fizycznie nie istnieje (nie jest doczytany), więc nie można uwzględnić jego serwisów podczas scalania modułów.

Leniwe doczytywane moduły są więc jednym ze sposobów osiągnięcia instancyjnych serwisów – wręcz narzuconym. Wygląda to następująco:

W zależności od tego, czy będziemy chcieli użyć CustomService czy CustomService2 mechanizm DI odpyta różne injectory. Albo główny, powstały w wyniki procesu kompilacji poprzez scalenie wszystkich nie-leniwych modułów, albo drugi utworzony podczas doczytania leniwego modułu. Odwołując się w komponencie nr. 5 do serwisu CustomService korzystamy z domyślnego injectora. Natomiast prosząc o CustoService2 korzystamy z nowego injectora leniwego modułu.

Aby uwspólnić injectory w leniwie doczytywanych modułach można posłużyć się metodą forRoot(), która blokuje powstanie dodatkowego injectora nawet w przypadku leniwych modułów. Z tej metody korzysta np. RouterModule i kilka innych fajnych Angularowych bibliotek, jednak jej opis to temat na osobny wpis.

Leniwe doczytywanie modułów aby osiągnąć instancyjne serwisy? Nie za bardzo. Coś tam się da, jednak nie jest to na pewno zadowalający efekt.

Redeklaracja serwisu w komponencie

Każdy serwis w Angularze deklarujemy w tablicy providers w jakimś module – czy to w głównym czy dodatkowym. Zapewnia to wspólny injector i oczywiście jedną singletonową instancję. Redeklaracja serwisu w dekoratorze komponentu powoduje utworzenie nowego injectora. Kod wygląda następująco:

@Component({
	selector: 'custom-component', 
	template: 'This is just a test component.',
	providers: [ CustomService ] // przełamanie łańcucha DI
})
class CustomComponent {
	constructor(private customService: CustomService) {
		// customService pochodzi z nowego injectora(!)
	}
}

Efekt działania powyższego kodu dla komponentu nr. 4 miałby następujący efekt:

Zarówno komponent nr. 4 jak i wszystkie jemu podległe prosząc o serwis CustomService dostałby instancję pochodzącą z nowego injectora. Komponentu należące do injectora będącego wyższej w hierarchii dziedziczenia (nr. 1, 2 i 3) dostaną inną instancję niż komponenty znajdujące się niżej w hierarchii (nr. 4, 5 i 6).

Należy pamiętać, że nowy injector przechowuje tylko i wyłączenie konkretny, redeklarowany serwis z tablicy providers umieszczonej w dekoratorze komponentu. Reszta serwisów ciągle będzie wczytywana z głównego injectora.

Rozwiązanie to jest bardzo skuteczne i ogólnie polecane. Jakie są jego wady? Często korzystając z metody forRoot() decydujemy się dostarczyć do serwisu jakąś dodatkową konfigurację już na etapie importu (podobnie jak w RouterModule). Korzystając z takiego rozwiązania decydujemy się na wielopostaciowość instancji danego serwisu, co może wiązać się z chęcią dostarczenia także unikalnej konfiguracji. Niestety nie za bardzo jest jak ją dostarczyć, chyba że jakąś osobną metodą (np. jakiś setter).

Ręcznie tworzony serwis

Opiszę moje ostatnie doświadczenie, które generalnie skłoniło mnie do napisania tego wpisu. Pewnego razu w pracy musiałem stworzyć uniwersalny moduł wyszukiwarki, który mógłby być użyty w wielu miejscach w projekcie. Był to moduł wyodrębniony do większego, reużywalnego cora.

Podejście 1

Początkowo był to zwykły singleton, do którego za pomocą składni metody forRoot() przesyłałem odpowiednią konfigurację np.:

@NgModule({

    imports: [
        CoreSearchModule.config({
            method: HttpMethods.GET,
            endpoint: '/api/articles',
            resultsPerPage: 10
        }),
    ],
	
})
export class AppModule { }

Rozwiązanie fajne, jednak wstrzykiwanie konfiguracji jest bezsensu. W innym miejscu w projekcie ktoś może chcieć strzelać do innego endpointa. Pierwsza myśl? Umożliwić przesłanie nowej konfiguracji jako parametr opcjonalny metody search(). Po chwili namysłu stwierdziłem – bezsensu. Serwis jest singletonem, nie mogę nadpisywać jego konfiguracji w rożnych miejscach aplikacji.

Podejście 2

Stwierdziłem, że serwis wyszukiwarki nie będzie serwisem @Injectable, tylko zwykła klasą TypeScripta. Chcąc stworzyć wyszukiwarkę do artykułów należałoby stworzyć ArticleSerachService dziedziczący z kolei z SearchService. Serwisy rozszerzające dałoby się już normalnie wstrzykiwać. Fajnie? No fajnie, działałoby. Każdy „pod serwis” miałby osobną instancję w injectorze, każdy mógłby mieć inną konfigurację. Jednak to podejście do rozwiązania problemu wydawało mi się bezsensowne. Po co tworzyć sztuczną warstwę nieprzydanych serwisów, tylko po to aby wygrać z singletonami Angulara?

Podejście 3

Stwierdziłem, że pokonam Angulara jego własną bronią – injectorami. Redeklaracja serwisu wyszukiwania w każdym komponencie, gdzie tylko będę chciał użyć serwisu wyszukiwarki. Rozwiązanie początkowe wydawało się fajne, nawet polecane przez dokumentację. Jednak niesie ze sobą kilka problemów:

  • brak możliwości dostarczenia konfiguracji
  • brak możliwości 2 osobnych instancji w obrębie jednego komponentu
  • totalny syf w kodzie (kto obcy kiedyś będzie wiedział, że w komponencie nr. 28 ktoś przełamał łańcuch DI redeklarując serwis?)

Szybko z tego podejścia zrezygnowałem.

Podejście 4

Ostatnia deska ratunku. Przerobiłem od deski do deski cały mechanizm DI Angulara w poszukiwaniu dobrego rozwiązania. Przerobiłem wszystkie możliwe operatory i strategie opisywane w różnych miejscach w internecie. Nic ciekawego nie znalazłem. Zrezygnowany stwierdziłem, że to czas przestać używać DI Angulara.

Utworzyłem zwykłą klasę – niewstrzykiwalną – pozbawioną dekoratora @Injectable. Tworzyłem instancję serwisu operatorem new w jakim miejscu chciałem, a konfigurację dostarczałem przez konstruktor. Prawie myślałem, że task jest skończony, ale stwierdziłem – beznadzieja. Nie po to Angular zapewnia mechanizm DI aby teraz robić takie kwiatki. Jak to później testować?

Ostatecznie satysfakcjonujące rozwiązanie wymyśliłem w ostatniej chwili. Opiszę je w kolejnym akapicie.

Funkcja konstruująca – pseudo fabryka

Dość długo myślałem nad rozwiązaniem swojego problemu: dostarczenie elastycznego, reużywalnego, konfigurowalnego serwisu, który byłby gotowy do użycia w każdym miejscu projektu, korzystając przy tym DI Angulara. Zakręciłem się myślami wokół wzorca budowniczy oraz wzorca fabryki. Stwierdziłem, że budowniczy może rozwiązać mój problem, jednak po chwili facepalm – typescript nie obsługuje klas zagnieżdżonych niezbędnych do stworzenia statycznego budowniczego. Użycie budowniczego w koncepcji GoF (ang. Gang of Fours) nie miało w tym wypadku sensu.

Ostatecznie stworzyłem rozwiązanie przypominające prostą statyczną fabrykę. Postanowiłem wykorzystać serwis Angularowy w postaci singletona, który za pomocą funkcji konstruującej (ang. constructor function) zwróci nową instancję interesującego mnie serwisu, który już nie jest zależy od mechanizmu DI Angulara – dzięki temu, nie jest singletonem. Wykonane kroki:

  1. Dodałem singletonowy serwis SearchService wstrzykiwalny przez DI Angulara (z dekoratorem @Injectable)
  2. SearchService zawiera tylko jedną metodę createSearch(), która dodatkowo przyjmuje odpowiednią konfigurację
  3. Powyższa metoda zwraca nową instancję serwisu SearchEngine, który nie jest już serwisem obsługiwanym przez DI Angulara
  4. Serwis SearchEngine przyjmuje interfejs z konfiguracją i wystawią całą funkcjonalność wyszukiwarki.

Myślę, że takie podejście to bardzo dobre rozwiązanie w przypadku radzenia sobie w wymagających przypadkach Angularowych singletonowych serwisów. Przykładowe użycie mojego rozwiązania wygląda następująco:

@Component({
    selector: 'custom-component', 
    template: 'This is just a test component.'
})
class CustomComponent {
	
    constructor(private searchService: SearchService) { }
	
    searchArticles() {
        this.searchService().createSearch<Article>({
            method: HttpMethods.GET,
            endpoint: '/api/articles',
            resultsPerPage: 10
        }).search({
            surename: 'Kowalski',
            group: 23
        }).subscribe((result) => {
            console.log(result);
        });
    }
}

Oczywiście instancję zwróconą przez metodę createSearch<T>() można także zapisać i reużywać:

@Component({
    selector: 'custom-component', 
    template: 'This is just a test component.'
})
class CustomComponent {
	
    private articlesSearch: ISearch<Article>;
    
    constructor(private searchService: SearchService) {
        this.articlesSearch = this.searchService().createSearch<Article>({
            method: HttpMethods.GET,
            endpoint: '/api/articles',
            resultsPerPage: 10
        });
    }
	
    searchArticles() {
        this.articlesSearch.search({
            surename: 'Kowalski',
            group: 23
        }).subscribe((result) => {
            console.log(result);
        });
    }
}

Dlaczego zdecydowałem się na taką metodę? Po co mieszać serwis zarządzany przez DI Angulara z instancyjnym serwisem? Oprócz listy wniosków , które opisałem w akapitach wyżej pojawia się kolejna bardzo ważna zaleta. Serwis zarządzany przez DI Angulara może dostarczyć naszemu „sub-serwisowi” inne serwisy także zarządzane przez mechanizm DI. Mój serwis do wyszukiwarki korzysta z HttpClient, który musi być serwowany przez DI Angulara. Dzięki obudowującemu singletonowi mogę zawsze pobrać instancję tego serwisu:

@Injectable()
export class SearchService {

    constructor(private http: HttpClient) { }

    /**
     * @description Factory method which return new instance of `SearchEngine<T>`
     * @param `searchConfiguration` base search configuration
    */
    createSearch<T>(searchConfiguration: SearchConfiguration) {
      return new SearchEngine<T>(searchConfiguration, this.http);
    }

}

Graficzne przedstawienie tej koncepcji wygląda następująco:

Kilka plusów tego podejścia:

  • brak singletonów
  • możliwość „wstrzyknięcia” konfiguracji
  • możliwość kilku różnych instancji serwisu w jednym komponencie
  • całość dobrze opisana TypeDoce’m, więc każdy programista bezproblemowo się w tym odnajdzie
  • nadal korzystamy z DI Angulara (dla serwisu fabrykującego), co zwiększa skalowalność projektu
  • nadal możemy wstrzykiwać i używać serwisy zarządzane przez mechanizm DI Angulara

Metodę createSearch<T>() oczywiście można pominąć, konfigurację wrzucić w konstruktor, a ten dalej zwróciłby nową instancję wyszukiwarki. Ta mała nadmiarowość z mojej strony miała dać do myślenia programiście kiedyś pracującym na tym kodzie – aby wiedział, że coś się tam dzieje, że nie jest to tylko zwykłe wywołanie konstruktora z parametrami. Nie jest to standardowe zachowanie serwisów Angularowych, więc lepiej to podkreślić.

Co sądzicie o takim podejściu? Być może ktoś zna inne, alternatywne rozwiązania radzenia sobie z niechcianymi singletonami?

]]>
https://www.p-programowanie.pl/angular/jak-pozbyc-sie-singletonow-w-angularze/feed/ 4