Archiwum autora: Techniczny Inside

JSON Web Tokens (JWT)

Temat, któremu chcemy się dzisiaj przyjrzeć to JWT, ostatnio bardzo często wykorzystywany w kontekście autoryzacji. Przyjrzymy się dokładniej czym jest JSON Web Tokens oraz w jaki sposób go generować i wykorzystywać w rzeczywistych przypadkach.

JWT – co to jest?

JWT (JSON Web Tokens) to otwarty standard (RFC 7519), który definiuje sposób wymiany danych między stronami w bezpieczny sposób poprzez obiekt JSON. Przesyłane informacje mogą być weryfikowane dzięki cyfrowemu podpisowi, który jest elementem tokenu.
Token JWT jest podpisany za pomocą sygnatury – algorytmem HMAC lub za pomocą klucza publicznego/prywatnego RSA lub ECDSA.

Kiedy JSON Web Tokens?

JWT może być wykorzystany przy:
1. Autoryzacji – JWT znajduje szerokie zastosowanie w autoryzacji kiedy jedna ze stron chce przyznać dostęp drugiej do zasobów i serwisów, a później bez przechowywania stanu po swojej stronie weryfikować czy dostęp powinien być możliwy.

2. Transmisji danych – Kiedy chcemy przesłać pomiędzy stronami informacje i potrzebujemy mieć pewność, że nadawca jest tym za kogo się podaje i dane które wysyła nie zostały zmienione. Możemy to zweryfikować właśnie dzięki cyfrowemu podpisowi, które jest częścią JWT.

Struktura JWT

JWT w swojej wynikowej postaci (jako token) składa się z trzech części oddzielonych od siebie kropkami.

Przykład:
aaaa.bbbb.cccc

Te części to kolejno:
• Nagłówek (Header)
• Zawartość (Payload)
• Sygnatura (Signature)

1. Nagłówek (Header)

Nagłówek zawiera informację o rodzaju tokena – JWT oraz o tym jakiego algorytmu używamy – HMAC SHA256 lub RSA.
Przykład:

{
  "alg": "HS512",
  "typ": "JWT"
}

Obiekt JSON w postaci wynikowej jest zmieniany na zapis w Base64.

2. Zawartość (Payload)

Ta część odpowiedzialna jest za przechowywanie danych, które chcemy przesyłać w tokenie. JWT wyróżnia trzy typy informacji zawartych w payload: Registered claims, Public claims oraz Private claims.
Oprócz danych identyfikacyjnych tokena oraz informacji o dacie ważności i kontekstu, w payload umieszczamy dane związane z rolą użytkownika, dostępem do zasobów, ustawieniami itp.

Przykład:

{
  "customerId": "123",
  "role": "customer"
}

Tak jak w przypadku nagłówka, payload też kodowany jest w formacie Base64.

3. Sygnatura (Signature)

Sygnatura jest podpisem cyfrowym potwierdzającym autentyczność danych zawartych w tokenie. Walidacja sygnatury daje nam pewność, że nadawca jest tym za kogo się podaje.
Pewność tą zyskujemy dzięki metodzie budowania sygnatury. Przyjrzyjmy się temu bliżej:

Jeśli wybierzemy algorytm haszowania np. HMAC SHA256 to sygnatura tworzoną będzie w następujący sposób:

HMACSHA256(base64UrlEncode(header) + ’.’ + base64UrlEncode(payload), secret)

Gdzie secret to nasze hasło potrzebne do haszowania sygnatury. Musimy pamiętać o tym, że secret powinien być długi i składać się z różnych znaków, ponieważ łamiąc secret jesteśmy w stanie podszyć się pod serwis autoryzacyjny i wprowadzić swoje dane w payload.
Przykład implementacji algorytmu generowania tokenu JWT napisany w NodeJS. W przykładzie skorzystamy z implementacji algorytmu haszowania i kodowania base64Url poprzez zewnętrzne biblioteki.

const hmacSha256 = require('crypto-js/hmac-sha256');
const base64url = require('base64url');

const header = {
    typ: 'JWT',
    alg: 'HS256'
};

const payload = {
    userId: 123,
    role: 'customer'
};

const secret = 'secret password';

const jwtToken = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
const signature = base64url.encode(hmacSha256(jwtToken, secret).toString());
const jwtSignedToken = jwtToken + '.' + signature;

console.log(jwtSignedToken);

function base64UrlEncode(item) {
    return base64url.encode(JSON.stringify(item));
};

Zastosowanie

JSON Web Tokens można wykorzystać np. do budowy serwisu autoryzacyjnego, w którym chcemy uwierzytelniać użytkowników aplikacji – przykład:

Tworzenie serwisu z możliwością autoryzowania użytkowników:

1. Budujemy back-end naszej aplikacji i udostępniamy REST API naszym aplikacją klienckim.
2. Tworzymy aplikację kliencką, która komunikuję się poprzez REST API z częścią back-endową.
3. Dołączamy serwis autoryzacyjny, za pomocą którego aplikacje klienckie będą uzyskiwały tokeny JWT a back-end będzie w stanie sprawdzić czy użytkownik powinien dostać dostęp do żądanego zasobu.

wpis tech

Podsumowanie

W tym wpisie omówiliśmy strukturę JWT i zasadę budowania tokenów. Widzimy również zastosowanie tego standardu i w jak łatwy sposób jesteśmy w stanie zarządzać uprawnieniami i autoryzacją użytkowników.

Źródło: https://jwt.io/

Autorem tekstu jest Kamil Moroń.

Odległość Levenshteina

Wstęp

Czy zastanawialiście się kiedyś, skąd wyszukiwarki „wiedzą”, co użytkownik miał na myśli pomimo popełnionych literówek w wyszukiwanym haśle? Odpowiedzią są algorytmy obliczające miarę odmienności napisów. W tym artykule, postaram się omówić jeden z nich, jak również spróbować zoptymalizować go tak, aby był bardziej użyteczny dla rozwiązań informatycznych.

Odległość Levenshteina

Odległość Levenshteina – bo o tym algorytmie dzisiejszy wpis będzie – jest to prosty algorytm wyznaczający miarę odmienności napisów, czyli w uproszczeniu wyznacza ilość operacji prostych, które muszą zostać wykonane, aby słowo A stało się słowem B. Operacja prosta, w tym przypadku to:

• wstawienie nowego znaku
• usunięcie znaku
• podmienienie znaku

Dla zobrazowania, aby napis „kot” stał się napisem „mol” potrzebujemy wykonać 2 operacje proste – zamienić literkę „k” na „m” oraz „t” na „l”. Więc miara odmienności słowa „kot” i „mol” jest równa 2.

Algorytm

Jak więc nauczyć komputer, aby też potrafił rozpoznawać tę różnicę w słowach? Władimir Lewensztejn zaproponował poniższy algorytm:

1. Określamy długość badanych słów, długość słowa głównego A to `m`, a długość słowa porównywalnego B to `n`.

2. Tworzymy macierz(tablicę) `K` o wymiarach (m+1) x (n+1) .

3. Pierwszy wiersz macierzy wypełniamy wartościami od 0 do `n`

4. Pierwszą kolumnę macierzy wypełniamy wartościami od 0 do `m`

5. Dla każdego znaku słowa A wykonaj (od 1 do `m`, ozn. Indeksu `i`):

6. Dla każdego znaku słowa B wykonaj (od 1 do `n`, ozn. Indeksu `j`):

◦ jeżeli `A[i]` jest taki sam tak `A[j]` to koszt jest równy zero, w przeciwnym wypadku koszt jest równy 1
◦ przypisujemy wartość pola macierzy `K[i][j]` obliczając minimalną wartość z:
▪ `K[i – 1][j – 1]` + koszt
▪ `K[i – 1][j]` + 1
▪ `K[i][j – 1]` + 1

7. Miarą odmienności jest wartość wpisana w `K[m][n]`

Stosując powyższy algorytm, przeanalizujmy słowa „kot” i „pole”:

1. Długość słów m 3, n 4

2. 3. 4. Tworzymy macierz z wypełnionymi wartościami.

1

5. Obliczamy wartości macierzy.

2

Znak P różni się od K więc koszt jest równy 1. Obliczamy wartości:

• `K[2 – 1][2]` + 1 = 2

• `K[2][2 – 1]` + 1 = 2

• `K[2 – 1][2 – 1]` + 1(koszt) = 1

Najmniejsza wartość to 1, więc tyle wpisujemy w pole `K[2][2]`.

Podobnie uzupełniamy resztę wartości z wiersza.

3

W tym przypadku znaki się od siebie nie różnią, więc koszt jest równy 0. Więc:

• `K[3 – 1][3]` + 1 = 3

• `K[3][3 – 1]` + 1 = 3

• `K[3 – 1][3 – 1]` + 0 = 1

I wpisujemy w pole `K[3][3]` wartość 1.

4

Implementacja

Implementacja algorytmu jest wkompilowana w interpreter PHP od wersji 4.0.1. Na potrzeby testów i optymalizacji zaimplementowałem go i można znaleźć go w repozytorium: https://gitlab.com/dysan12/levenshtein_distance

Optymalizacja

Wykonując krokowo algorytm, możemy zauważyć pewną zależność, dzięki której nie musimy wykonać wszystkich obliczeń, aby szacować odmienności podanych napisów. Wystarczy, że przy graficznej interpretacji „poprowadzimy” ukośną linię z wynikowego narożnika macierzy.

5

W tym wypadku, już po uzupełnieniu pierwszego wiersza, wiemy że odmienność napisów, będzie wynosiła co najmniej 2.

Szacunek ten możemy przykładowo wykorzystać w sytuacji, gdy chcemy poznać wszystkie słowa z pewnego zbioru, których odmienność od badanego słowa nie będzie większa od zadanej.

Implementacja

Przyjmując kontynuację algorytmu krokowego oraz , że `max` to zadana maksymalna różnica słów:
Dodajemy do każdej iteracji kroku piątego warunki kończące wykonywanie

• jeśli porównywane słowa mają taką samą długość, to sprawdź czy wartość `K[i][j]` > `max`
• jeśli słowo porównywane B jest dłuższe niż słowo główne A, to sprawdź czy wartość `K[i][i + n – m]` > `max`
• jeśli słowo porównywane B jest krótsze niż słowo główne A, to sprawdź czy wartość `K[i][i – (n – m)]` > `max`

Jeśli algorytm przedwcześnie zakończył swoją pracę, tzn. że badane słowa zbyt różnią się od siebie.

40080793_352743705265754_3288724306145574912_n

Ilustracja 1: Optymalizacja zrealizowana w PHP

Wyniki

Testy zostały przeprowadzone na ponad 1000 elementowym zbiorze porównywanych słów z wyznaczoną maksymalną odległością od 0 do 10.

Aspekty:

• zoptymalizowany algorytm vs algorytm bez optymalizacji
• zoptymalizowany algorytm vs wbudowany w PHP(bez optymalizacji)

Gdzie:

• „Optimized algorithm time” to czas wykonania zoptymalizowanego algorytmu
• „Standard algorithm time” to czas wykonania algorytmu bez optymalizacji
• „Saved time” to zaoszczędzony czas
• „Percentage ratio” to stosunek czasów. Jeśli wartość jest mniejsza niż 100% to zoptymalizowany algorytm wykonywał się dłużej.

Zoptymalizowany algorytm vs algorytm bez optymalizacji

6

Z wyników widzimy, że optymalizacja, przy niskiej zadanej maksymalnej odległości, polepsza do 212% wydajność co daje ponad dwukrotnie skrócony czas obliczania.

Zoptymalizowany algorytm vs wbudowany w PHP(bez optymalizacji)

7

Wbudowana funkcja obliczania odległości levenshteina jest skompilowana w interpreter PHP, przez co wydajnościowo jest niejednokrotnie ponad 80 razy szybsza niż nieskompilowany algorytm – co nie było zaskoczeniem.

Wszystkie testy wraz z badanym zbiorem danych znajdują się w
https://gitlab.com/dysan12/levenshtein_distance/blob/master/test/LevenshteinTest.php

Podsumowanie optymalizacji

Z przeprowadzonych testów widzimy, że optymalizacja rzeczywiście skraca czas wykonania algorytmu, lecz aby z niej w praktyce skorzystać w języku interpretowanym, musielibyśmy wkompilować ją w interpreter.

Autorem tekstu jest Michał Gaj.

ANTLR

ANTLR – ANother Tool for Language Recognition

Na pewno spotkałeś się kiedyś z niestandardowymi strukturami danych, które ciężko przetworzyć w konwencjonalny sposób. W tym wpisie postaram się pokazać jak możesz poradzić sobie z takim problemem.

Czym jest ANTLR?

ANTLR – to generator parsera do czytania, wykonywania lub tłumaczenia binarnych oraz tekstowych struktur danych. Standardowy projekt procesowania danej struktury danych składa się z pliku Grammar zawierającego reguły dla Lexera oraz Parsera, które zaś używane są do tokenizacji oraz przetwarzania danych według wcześniej stworzonych reguł.

Ciekawostka

Zastanawiałeś się kiedyś jak działa analiza składni, dla przykładu w Netbeans? Jeśli tak, to pewnie nie zaskoczę Cię informacją, że używany jest tam między innymi ANTLR do analizy składni języka C++.

Ponad to, znany pewnie wszystkim Twitter, używa ANTLR do przetwarzania zapytań dla wyszukiwania. Są to ponad 2 miliardy zapytań na dzień. Ilość robi wrażenie? Na mnie tak.

Do czego możemy użyć ANTLR w praktyce?

Do wielu rzeczy, począwszy od przetwarzania niestandardowych strukturalnie plików konfiguracyjnych, przez konwertery tzw. ,,legacy code”, po różnego rodzaju parsery, renderowanie znaczników i tym podobne. Możliwości jest na prawdę sporo.

W jakich językach programowania można tego użyć?

W zasadzie we wszystkich najpopularniejszych językach: Java, C#, C++, Python, Swift, Go (po za PHP, chociaż swego czasu widziałem gdzieś, zrobiony port dla tego języka). Dla każdego z tych języków ,,kompilator” gramatyczny wygeneruje parser oraz lexer do przetwarzania zdefiniowanej przez nas struktury danych.

Przykład użycia

Na potrzeby tego wpisu, językiem programowania przy użyciu, którego będę prezentować możliwości ANLTR to C# (miłośnicy Javy muszą mi wybaczyć 🙂 ). Natomiast samo narzędzie ANLTR wykorzystam w jego najnowszej wersji, na dzień tworzenia tego wpisu czyli V4.

*Procesowanie przykładowej prostej struktury danych:

bq. # Comment
TypeId = 860
Name        = "Example Item"
Description = "Description of example item"
Attributes  = {Waypoint=0, Class=1}
Flags       = {Block, Pickable}

Struktura dosyć prosta, zapewne można by bez większego problemu przetworzyć takie dane przy użyciu choćby regexów. Jednak, chciałbym na tym dosyć trywialnym przykładzie pokazać jak działa ANTLR.

Plik grammar (.g4) wygląda w ten sposób:

grammar Objects;
 
root:
    items+
    ;
 
items:
    ('TypeId' '=' typeId) ('Name' '=' name) ('Description' '=' description)? ('Attributes' '=' attributes)? ('Flags' '=' flags)?
    ;
typeId:
     NUMBER
    ;
name:
    TEXT
    ;
description:
    TEXT
    ;
flags:
    ('{') flag+ ('}')
    ;
flag:
    STRING
    ;
attributes:
    ('{')  attribute+ ('}')
    ;
attribute:
    attributeName STRING?'=' attributeValue
    ;
attributeName:
    STRING;
attributeValue:
    NUMBER;
 
fragment DIGIT:
   ('-')? '0'..'9'
;
 
TEXT:
    '"' .*? '"'
;
 
STRING:
    ('a'..'z' | 'A'..'Z')+
;
 
NUMBER:
    DIGIT+
;
 
WHITESPACE:
    ' ' -> skip
;
 
COMMA_SEPARATOR :
    ',' -> skip
;
 
NEWLINE :
    ('\n' | '\r' | '\r\n') -> skip
;
 
COMMENT     :
    '#' ~( '\r' | '\n' )* -> skip
    ;

Kompilator na podstawie gramatyki zawartej w pliku grammar utworzy w docelowym języku (w naszym przypadku C#) parser oraz lexer przy użyciu których możemy skorzystać z możliwości przetworzenia wskazanych danych.

t1

Powstałe drzewo wygląda dosyć prosto i przejrzyście. Oczywiście w przypadku większej ilości danych, drzewo rozszerzy się o kolejne elementy.

t2

Jak z wydajnością takiego rozwiązania? To zależy od skomplikowania danych, ale mogę z czystym sumieniem powiedzieć, że w większości przypadków, przy odpowiednio stworzonej gramatyce, wydajność jest na zadowalającym poziomie.

Przykładowa klasa Reader

using Antlr4.Runtime;
using Objects.Antlr;
using Antlr.Visitor;
using Objects.Domain;
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Objects
{
    class ObjectsReader
    {
        private readonly string objectsFilePath;
 
        public ObjectsReader(string objectsFilePath)
        {
            this.objectsFilePath = objectsFilePath;
        }
 
        public List Read()
        {
            var objectsFileContent = System.IO.File.ReadAllText(objectsFilePath);
 
            var inputStream = new AntlrInputStream(objectsFileContent);
            var objectsLexer = new ObjectsLexer(inputStream);
            var commonTokenStream = new CommonTokenStream(objectsLexer);
 
            var objectsParser = new ObjectsParser(commonTokenStream);
            var itemsContext = objectsParser.root().items();
 
            var visitor = new ItemVisitor();
 
            foreach(var itemContext in itemsContext)
            {
                visitor.Visit(itemContext);
            }
 
            // Do something with parsed item's
        }
    }
}

Klasa Visitor z implementacją interfejsu z biblioteki ANTLR. Visitor służy do procesowania określonej części powstałego drzewa, danej struktury danych.

using Antlr4.Runtime.Misc;
using Objects.Domain;
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Antlr.Visitor
{
    class ItemVisitor : ObjectsBaseVisitor
    {
        public override Item VisitItems([NotNull] ObjectsParser.ItemsContext context)
        {
            var typeId = context.typeId().GetText();
            var name = context.name().GetText();
            var flags = context.flags();
            var description = context.description();
            var attributes = context.attributes();
 
            // Container
            var Item = new Item {
                Id = Convert.ToInt32(typeId),
                Name = name
            };
 
            if (null != description)
            {
                var descriptionText = description.GetText();
                Item.Description = descriptionText;
            }
 
            if (null != flags)
            {
                var _flags = flags.flag();
 
                foreach (var _flag in _flags)
                {
                    var flagText = _flag.GetText();
 
                    try
                    {
                        ItemFlag itemFlag = (ItemFlag)Enum.Parse(typeof(ItemFlag), flagText);
                        Item.AddFlag(itemFlag);
                    }
                    catch(ArgumentException)
                    {
                        Console.WriteLine("Undefined flag: {0}", flagText);
                    }
                }
            }
 
            if (null != attributes)
            {
                var _attributes = attributes.attribute();
 
                foreach(var _attribute in _attributes)
                {
                    var attributeType = _attribute.attributeName().GetText();
                    var attributeValue = _attribute.attributeValue().GetText();
 
                    try
                    {
                        ItemAttributeType itemAttributeType = (ItemAttributeType)Enum.Parse(typeof(ItemAttributeType), attributeType);
                        Item.AddAttribute(new ItemAttribute(itemAttributeType, Convert.ToInt32(attributeValue)));
                    }
                    catch (ArgumentException)
                    {
                        Console.WriteLine("Undefined attribute: {0}", attributeType);
                    }
                }
            }
 
            // Do something with parsed data
 
            return base.VisitItems(context);
        }
    }
}

Kod w klasach jest jedynie przykładem użycia parsera niżeli działającą wersją.

Po więcej informacji zapraszam na stronę projektu: http://www.antlr.org/index.html

Autorem tekstu jest Miłosz Lenczewski.

JMeter – praktycznie zastosowanie

Część ogólna

Apache JMeter jest narzędziem do przeprowadzania testów obciążeniowych (wydajnościowych). Testy te są bardzo ważną częścią każdego serwisu – pozwalają oszacować jak duży ruch jest w stanie obsłużyć nasz serwer www, baza danych itp. Niżej wymieniony wpis będzie dotyczył wykorzystania JMetera do przeprowadzania testów obciążeniowych np. aplikacji www. Wersja programu – 3.1. Każda z niżej wymienionych opcji dostępna jest po kliknięciu prawym klawiszem myszy.

Na początku musimy zdefiniować grupę wątków. Znajdziemy go w Test plan → Add → Threads (Users) → Thread group

Grupa wątków służy do zarządzania ilością żądań, które będą wysyłane w ciągu jednej sekundy:

Number Of Threads – ilość wątków używanych do wysyłania żądań,
Ramp-Up Period – w ciągu ilu sekund mają zostać wysłane żądania,
Loop Count – ile razy dany test ma się powtórzyć,
Delay Thread creation until needed – bez oznaczonej tej opcji, JMeter przydziela od razu całą pamięć dla wszystkich wątków. Oznacza to, że nawet jeżeli użytkownik wykona swoją próbkę po np. 30 minutach to pamięć będzie dla niego zarezerwowana od razu po uruchomieniu skryptu
Scheduler – możemy ustawić w jaki dzień i o której godzinie chcemy uruchomić test.

Dla przykładu, jeżeli podamy number of thread na 100, ramp-up period na 10, wtedy na każdą sekundę będzie wysyłanych 10 użytkowników (żądań).

t1

Następnym krokiem będzie skonfigurowanie testu dla wcześniej utworzonej grupy wątków. Jako, że żądania JMetera są ubijane przez wiele serwisów (brak odpowiedniej identyfikacji), na początku należy skonfigurować nagłówek z User-Agent. Znajdziemy go w Thread Group → Add → Config Element → HTTP Header Manager.

t2

Kolejnym krokiem będzie zdefiniowanie domyślnego adresu url. Jest to o tyle ważne, że usprawnia proces zmiany głównej ścieżki. Może zmienić się nazwa domeny z np. teststrona.pl na testowy.pl. Jeżeli nie przygotowalibyśmy domyślnego adresu należałoby zmienić adres w każdym kroku testu gdzie wykonujemy żądania na sprecyzowany adres url np. test.pl/home. Znajdziemy go w Thread Group → Add → Config Element → HTTP Request Defaults.

t3

Jeżeli chcemy, aby każde żądanie pobierało nam dodatkowo zawartość dynamiczną strony (zdjęcia, js itp.), należy w HTTP Request Default lub w każdym HTTP Request z osobna, oznaczyć Retrieve All Embedded Resources. Parallel Downloads wskazuje ile ma być równolegle pobieranych zasobów, a URLs must match wskazujemy z jakiego adresu url chcemy pobierać zasoby.

t4

Dla przykładu wysłałem dwa żądania na ten sam adres url. Jeden pobiera zawartość dynamiczną a drugi statyczną:

t5

Następnie definiujemy Random Controller. Dodajemy go w celu zwiększenia losowości wykonywanego testu. Po jego użyciu wybierana jest tylko jedna akcja z kilku dostępnych w ramach danego wątku w określonym teście. Thread Group → Add → Logic Controller → Random Controller.

t6

Dla Random Controllera dodajemy HTTP Request. Służą one do wskazywania na jaki adres url mają być wysyłane żądania. W poprzednim kroku zdefiniowaliśmy HTTP Request Default, więc teraz pozostaje wskazać już tylko ścieżkę docelową. Thread Group → Add → Sampler → HTTP Request

t7

Dodatkowo w tym miejscu możemy definiować:

protocol – jaki to jest protokół tzn http/https,
method – jaką metodą ma być wysyłane żądanie dla zasobu (GET, POST itp.),
path – podajemy tutaj naszą docelową ścieżkę,
parameters/body data – możemy przekazać dodatkowe parametry/dane

Bardzo ważnym elementem są również słuchacze. Zbierają one różne dane o próbkach (żądaniach), w zależności od wybranego elementu. Oczywiście można wybrać każdy z nich: Thread Group → Add → Listener → View Results in Table

t8

Należy pamiętać, że dużym ograniczeniem dla JMeter-a są zasoby oraz łącze. Podczas działania testu zużywane są spore zasoby pamięci ram oraz łącza.

Uruchamianie JMeter-a poza GUI

Komendą do uruchamiania JMeter-a poza GUI jest:

sh jmeter -n -t /home/dawid/testObciazeniowy.jmx

Aby uruchomić test tą komendą musimy znajdować się w katalogu gdzie zainstalowaliśmy Apache JMeter → bin

Bardzo ważne są domyślne ograniczenia Javy, które wynoszą raptem 1 GB pamięci ram. Z doświadczenia wiem, że jest to bardzo mało, jeżeli chcemy zasymulować duży ruch. W przypadku gdy chcemy ustawić większą ilość pamięci dla procesów Javy wykonujemy nasz skrypt z dodatkowymi parametrami:

JVM_ARGS="-Xms13000m -Xmx13000m -XX:NewSize=13000m -XX:MaxNewSize=13000m" && export JVM_ARGS && sh jmeter -n -t /home/dawid/testObciazeniowy.jmx

Dodatkowo nie uruchamiając testu poprzez GUI możemy otrzymać jego wynik w pliku. Należy do naszej komendy dodać jeszcze jeden parametr. Finalna komenda wygląda następująco:

JVM_ARGS="-Xms13000m -Xmx13000m -XX:NewSize=13000m -XX:MaxNewSize=13000m" && export JVM_ARGS && sh jmeter -n -t /home/dawid/testObciazeniowy.jmx -l /home/dawid/wynikTestu.csv

Uruchomienie skryptu – widok terminal:

t9

Wynik w postaci pliku csv:

t10

Wyniki testu

Poniżej przedstawię wyniki testu, który przeprowadzałem na jednym z naszych sklepów. Narzędzie wykorzystane do rejestrowania ruchu to relic. Jak widać na screenie ruch wyrażony jest w rpm czyli „requests per minute” (żądania na minutę):

t11

Podsumowanie testu w pliku prezentuje się w następujący sposób:

t12

Screen jest podglądowy, ogółem wszystkich wierszy jest dużo więcej.

Autorem tekstu jest Dawid Adamczyk.

Krótko o SOLID

Podstawowych zasad programowania obiektowego jest wiele. W dzisiejszym wpisie rozważymy jedną z nich, zaproponowaną przez Roberta C. Martina – SOLID.

SOLID to mnemonik pięciu zasad:

S – SRP – Single responsibility principle,
O – OCP – Open/Closed principle,
L – LSP – Liskov substitution principle,
I – ISP – Interface segregation principle,
D – DIP – Dependency inversion principle.

W kilku zdaniach chciałbym przedstawić podstawowe założenia i przykłady dla każdej z tych reguł.

S – zasada jednej odpowiedzialności

Zgodnie z tą zasadą każda klasa powinna mieć tylko jedną odpowiedzialność (innymi słowy: powinien istnieć tylko jeden powód na modyfikację klasy).

Spróbujmy zatem przeanalizować przykładową klasę:

class Person {
    /** string */
    private $firstName;
    /** string */
    private $lastName;

    /** string */
    private $email;

    /** string */
    private $streetName;
    /** string */
    private $buildingNumber;
    /** string */
    private $apartmentNumber;
    /** string */
    private $postalCode;
    /** string */
    private $city;

    public function __construct(string $firstName, string $lastName, string
$email, string $streetName, string $buildingNumber, string $apartmentNumber, string $postalCode, string $city)
    {
        $this->firstName = $firstName;
        $this->lastName = $lastName;
        $this->email = $this->validateEmail($email);
        $this->streetName = $streetName;
        $this->buildingNumber = $buildingNumber;
        $this->apartmentNumber = $apartmentNumber;
        $this->postalCode = $postalCode;
        $this->city = $city;
}

    private function validateEmail(string $email) : string {
    if (false === strpos($email, '.') || false === strpos($email, '@'))
{

        throw new Exception('Invalid email');
    }
        return $email;
    }
}

Klasa Person łamie zasadę jednej odpowiedzialności w przynajmniej kilku miejscach. Po pierwsze – pola dotyczące adresu. Klasa nie powinna zawierać pól, które nie są z nią powiązane. Bez problemu można wydzielić je do osobnego obiektu co znacznie ułatwi przyszły refactoring tej klasy (np. dodanie adresu zameldowania, dostawy etc.).

Drugim błędem jest walidator adresu e-mail. Sprawdzenie poprawności adresu nie leży w obowiązku klasy Person. Do tego sama metoda rzuca wyjątek co również jest błędem.

Poprawny kod wygląda np. tak:

class Address {
    /** string */
    private $streetName;
    /** string */
    private $buildingNumber;
    /** string */
    private $apartmentNumber;
    /** string */
    private $postalCode;
    /** string */
    private $city;

    public function __construct(string $streetName, string $buildingNumber,
string $apartmentNumber, string $postalCode, string$city)
    {
        $this->streetName = $streetName;
        $this->buildingNumber = $buildingNumber;
        $this->apartmentNumber = $apartmentNumber;
        $this->postalCode = $postalCode;
        $this->city = $city;
   } 
}

class Person
{
    /** string */
    private $firstName;
    /** string */
    private $lastName;

    /** string */
    private $email;

    /** @var Address */
    private $address;

    public function __construct(string $firstName, string $lastName, string
$email, Address $address)
    {
        $this->firstName = $firstName;
        $this->lastName = $lastName;
        $this->email = $email;
        $this->address = $address;
    }

    public function getEmail() : string {
        return $this->email;
    } 
}

class EmailValidator
{
    public static function isValid(string $email): bool
    {
        $hasDot = false !== strpos($email, '.');
        $hasAt = false !== strpos($email, '@');

        return $hasDot && $hasAt;
    }
}

Poprzednia klasa została rozbita na trzy nowe. Każda z nich odpowiada tylko za jedną rzecz. Dodatkowo klasa metoda walidująca poprawność adresu e-mail nie zawiera logiki rzucania wyjątku. Dzięki temu można ją wykorzystać ponownie.

O – zasada otwarte-zamknięte

Zasada OCP oznacza, że klasa powinna być „otwarta na rozszerzenia a zamknięta na modyfikacje”. Uściślając – nie powinno dojść do sytuacji w której trzeba modyfikować kod. Jest to zabronione, gdyż zmiana deklaracji metody może spowodować błędne działanie w innych miejscach systemu.

Rozważmy taki fragment kodu:

class Square {
    /** @var float */
    public $a;


    public function __construct(float $a)
    {
         $this->a = $a;
    }
}

class Circle{
    /** @var float */
    public $r;

    public function __construct(float $r)
    {
        $this->r = $r;
    }
}

class AreaCalculator {

    public function calculate($shape) : float {
        if ($shape instanceof Square) {
            return $shape->a * $shape->a;
        }
        elseif ($shape instanceof Circle) {
            return M_PI * $shape->r * $shape->r;
        }

        return 0.0; 
    }
}

$square = new Square(3);
$circle = new Circle(5);
$calculator = new AreaCalculator;

echo $calculator->calculate($square) . PHP_EOL;
echo $calculator->calculate($circle);

Metoda calculate() z klasy Calculator jest błędna. Dodanie każdej nowej figury wymusza zmianę tej metody, co jest niezgodne z zasadą OCP.

Poprawna implementacja może wyglądać tak:

interface Shape {
    public function area() : float;
}

class Square implements Shape {
    /** @var float */
    public $a;

    public function __construct(float $a)
    {
        $this->a = $a;
    }

    public function area(): float
    {
         return $this->a * $this->a;
    }
}

class Circle implements Shape {
    /** @var float */
    public $r;
 
    public function __construct(float $r)
    {
        $this->r = $r;
    }

    public function area(): float
    {
        return M_PI * $this->r * $this->r;
    }
}

class AreaCalculator {
    public function calculate(Shape $shape) : float {
        return $shape->area();
    }
}

$square = new Square(3);
$circle = new Circle(5);
$calculator = new AreaCalculator;

echo $calculator->;calculate($square) . PHP_EOL;
echo $calculator->;calculate($circle);

Został dodany interfejs, który wymusza implementacje metody area(). Dzięki temu dodając np. trójkąt możemy przekazać go naszemu kalkulatorowi a ten poprawnie zwróci pole figury. Nie wymaga to żadnej zmiany w kalkulatorze.

L – zasada podstawienia Liskov

Ta zasada mówi o tym, że w miejscu klasy bazowej możemy użyć dowolnej klasy, która po niej dziedziczy. Oznacza to, że klasa pochodna musi zachować 100% interfejsu klasy bazowej (wszystkie metody muszą przyjmować te same argumenty i zwracać te same typy).

Spójrzmy na przykład:

class Rectangle {
    private $width;
    private $height;

    public function getWidth() {
        return $this->width;
}

    public function setWidth($width) {
        $this->width = $width;
}

    public function getHeight() {
        return $this->height;
}

    public function setHeight($height) {
        $this->height = $height;
}

    public function getArea() {
        return $this->width * $this->height;
    }
}

class Square extends Rectangle {
     public function setWidth($value) {
        $this->width = $value;
        $this->height = $value;
    }
    public function setHeight($value) {
        $this->width = $value;
        $this->height = $value;
    }
}

Zgodnie z zasadami matematyki wszystko jest w porządku. Każdy kwadrat jest przecież prostokątem. Ale czy w programowaniu rzeczywiście tak jest?

Przeanalizujmy przykładowe użycie:

class Client {
    public function areaVerifier(Rectangle $rectangle)
    {
        $rectangle->setWidth(5);
        $rectangle->setHeight(4);

        return $rectangle->getArea() == 20;
    }
}

class LspTest extends PHPUnit_Framework_TestCase {
    public function testRectangleArea()
    {
        $rectangle = new Rectangle;
        $client = new Client;

        $this->assertTrue($client->areaVerifier($rectangle));
    }

    public function testSquareArea()
    {
        $square = new Square;
        $client = new Client;
 
        $this->assertTrue($client->areaVerifier($square));
    }
}

Pierwszy test przejdzie prawidłowo. Drugi nie. Nasz kwadrat nie zachowuje się jak prostokąt. Łamie prawa geometrii. Ten przykład przy okazji pokazuje, że została złamana nie tylko zasada LSP. Widać tutaj, że programowanie zorientowane obiektowo nie polega na zobrazowaniu prawdziwego życia obiektów. Jeśli będziemy próbować odwzorować jeden do jednego rzeczywistość, prawie zawsze się zawiedziemy.

Zobaczmy jeszcze taki przykład:

class BlackCoffeMachine {
    public function brew() {
        echo 'Pour coffe to the cup' . PHP_EOL;
        echo 'Pour water to the cup' . PHP_EOL;
    }
}

class WhiteCoffeMachine extends BlackCoffeMachine {
    public function brew() {
        parent::brew();
        echo 'Pour milk to the cup' . PHP_EOL;
    }
}

$machines = [
    new BlackCoffeMachine,
    new WhiteCoffeMachine
];

foreach ($machines as $machine) {
    $machine->brew();
}

Zachowuje on zasadę podstawienia. Metoda brew() rozszerza metodę z klasy bazowej zachowując kontrakt, w związku z tym obie klasy mogą być używane wymiennie.

I – Segregacja interfejsów

Zasada mówi, że nie powinno się wymuszać implementacji interfejsów które nie są używane. Innymi słowy: klienci nie powinni zależeć od interfejsów, których nie używają. Oznacza to, że lepiej zdefiniować wiele mniejszych interfejsów niż jeden wielki.

 interface WorkerInterface {
    public function work();
    public function sleep();
}

class HumanWorker implements WorkerInterface {
    public function work() {
        return 'human working';
}

    public function sleep() {
        return 'human sleeping';
    }
}

class AndroidWorker implements WorkerInterface {
    public function work() {
        return 'android working';
    }
    public function sleep() {
        return null;
    }
}

class Capitan {
    public function manage(WorkerInterface $worker) {
        $worker->work();
        $worker->sleep();
    }
}

Robot nie potrzebuje snu, więc implementacja metody sleep() zwraca null. Można tu przy okazji zauważyć złamanie zasady LSP (różne typy zwracanych danych dla różnych implementacji).

Spróbujmy przeanalizować poprawiony kod:

interface WorkableInterface {
    public function work();
}

interface SleepableInterface {
    public function sleep();
}

class HumanWorker implements WorkableInterface, SleepableInterface {
    public function work() {
        return 'human working';
    }

    public function sleep() {
        return 'human sleeping';
    }
}

class AndroidWorker implements WorkableInterface {
    public function work() {
        return 'android working';
    }
}

Interfejsy zostały podzielone, każdy obiekt implementuje tylko to, czego rzeczywiście wymaga. Ale co z klasą kapitana? Czy może ona wyglądać tak:

class Capitan {
    public function manage($worker) {
        $worker->work();
        if ($worker instanceof SleepableInterface) {
            $worker->sleep();
        } 
    }
}

Oczywiście, że nie. Jest tu złamana zasada OCP. Przykładowa poprawna implementacja może wyglądać tak:

interface WorkableInterface {
    public function work();
}

interface SleepableInterface {
    public function sleep();
}

interface ManageableInterface {
    public function beManaged();
}

class HumanWorker implements WorkableInterface, SleepableInterface,
ManageableInterface {
    public function work() {
        return 'human working';
    }

    public function sleep() {
        return 'human sleeping';
    }

    public function beManaged() {
        $this->work();
        $this->sleep();
    }
}

class AndroidWorker implements WorkableInterface, ManageableInterface {
    public function work() {
        return 'android working';
    }

    public function beManaged() {
        $this->work();
    }
}

class Capitan {
    public function manage(ManageableInterface $worker) {
        $worker->beManaged();
    }
}

D – Dependency Inversion

Zasada Odwrócenia Zależności mówi o tym, że moduły wysokopoziomowe nie powinny zależeć od modułów niskopoziomowych. Wszystkie zależności powinny w jak największym stopniu zależeć od abstrakcji, a nie od konkretnego typu.

Spójrzmy na ten prosty przykład:

class MySQLConnection {
   public function connect()
   {
       //
   }
}

class PasswordReminder {
    private $dbConnection;

    public function __construct(MySQLConnection $connection)
    {
        $this->dbConnection = $connection;
    }
}

Nasza klasa do przypominania haseł wymaga połączenia do bazy danych. W tym przypadku wymagany jest obiekt klasy MySQLConnection. A co, gdybyśmy chcieli zmienić silnik bazy danych? Dla klasy PasswordReminder nie powinno mieć znaczenia, jaki jest typ bazy danych. Można to w łatwy sposób naprawić:

interface DbConnectionInterface {
    public function connect();
}

class MySQLConnection implements DbConnectionInterface {
    public function connect()
    {
        // 
    }
}

class InFileConnection implements DbConnectionInterface {
    public function connect()
    {
        // 
    }
}

class PostgresConnection implements DbConnectionInterface {
    public function connect()
    {
        //
    }
}

class PasswordReminder {
    private $dbConnection;

    public function __construct(DbConnectionInterface $connection)
    {
        $this->dbConnection = $connection;
    }
}

W ten prosty sposób uniezależniliśmy przypominanie haseł od jakiegokolwiek silnika. Teraz jego zależnością jest połączenie z bazą a nie konkretny silnik. Sprawiliśmy, że zależność wymaga abstrakcji a nie konkretnej implementacji.

Podsumowanie

Znajomość zasad SOLID na etapie projektowania aplikacji pozwala uniknąć wielu błędów. Stosując je znacząco ułatwimy sobie rozwój naszej aplikacji w przyszłości. Wyrobienie dobrych praktyk programistycznych z pewnością zapunktuje w przyszłym życiu zawodowym.

Tym wpisem chciałem tylko zasygnalizować, że jest coś takiego jak SOLID. Pokazałem kilka prostych przykładów, jednak temat jest dużo szerszy. Mam nadzieję, że zaciekawiłem Cię na tyle, że zaczniesz stosować je w swoim życiu.

Opracowano na podstawie https://laracasts.com/series/solid-principles-in-php/

Autorem tekstu jest Łukasz Wojtyczka.

Praktycznie zastosowanie funkcji tablic

Funkcje tablic to bardzo bogate narzędzia ułatwiające pracę programisty PHP. Możemy przy ich pomocy modyfikować zawartość tablicy, sortować, wykonywać na każdym elemencie jakąś instrukcję czy też dowolnie przekształcać. W poniższym wpisie chciałbym przedstawić kilka praktycznych przykładów pokazujących w czym te funkcje mogą nam się przydać. Przykłady te nie wyczerpują tematu, ale mam nadzieję, że chociaż w minimalnym stopniu zaznajomią z tematem.

Umieszczanie elementu w środku tablicy

Wyobraźmy sobie sytuację, w której chcemy dodać nowy element do tablicy. Aby dopisać element na końcu tablicy możemy użyć array_push, na początku używamy array_shift, a co żeby umiescić element gdzieś w środku tablicy? Z pomocą przychodzi nam funkcja array_splice, która służy do usunięcia części tablicy i zamienia ją na coś innego.

<?php

$arr = ['A', 'B', 'D'];


array_splice($arr, 2, 0, ['C']);


print_r($arr);

W powyższym przykładzie mamy tablicę, w której znajdują się kolejne elementy alfabetu z jedną brakująca literą. Aby poprawić nasz alfabet usuwamy „zero” elementów i dodajemy brakujące C. Dla powyższego kodu otrzymamy poprawne i kompletne pierwsze cztery litery alfabetu.

Array

(

    [0] => A

    [1] => B

    [2] => C

    [3] => D

)

Usuwanie elementów

Jeśli chodzi o usuwanie pierwszego i ostatniego elementu tablicy to możemy posłużyć się funkcjami array_pop oraz array_unshift, ale przypuśćmy, że chcemy usunąć jakiś obiekt z tablicy wskazując jego właściwości. Przykładwo chcemy usunąć obiekt instancji klasy Foo który posiada id 18.

<?php


class Foo

{

    private $id;

    public function __construct($id)  {

        $this->id = $id;

    }

    public function getId() {

        return $this->id;

    }

}


$arr = [new Foo(17), new Foo(18),  new Foo(16)];


$idToDelete = 18;


$result = array_filter($arr, function(Foo $foo) use($idToDelete) {

    return $foo->getId() !== $idToDelete;

});

Wykorzystując funkcję array_filter w zmiennej $result mamy tablicę bez obiektu o id 18. array_filter wywoływana bez parametrów zwróci tablice usuwając z tej wejściowej takie elementy jak w puste stringi oraz null.

Posiadając tablicę składającą się z typów prostych takich jak np string możemy wykorzystać funkcję array_search, która pozwoli nam wyszukać index w tablicy pod którym znajduje się dany element.

<?php


$arr = ['A', 'B', 'X', 'C'];


if (($key = array_search('X', $arr, $strict = true)) !== FALSE) {

    unset($arr[$key]);

}


print_r($arr);

W wyniku wywołania powyższego kodu otrzymamy tablicę bez 'iksa’.

Array

(

    [0] => A

    [1] => B

    [3] => C

)

Jeśli problematyczne okaże się to, że nie mamy ciągłości w kluczach tablicy (brakuje wartości pod drugim indeksem) możemy wykorzystać funkcję array_values.

$arr = array_values($arr);

Dopisanie powyższego kodu za klamrą zamykającą z warunku w poprzednim przykładzie zmieni wynik na taki posiadający ciągłość w kluczach.

Array

(

    [0] => A

    [1] => B

    [2] => C

)

Sortowanie tablicy po wartości obiektu

Dla prostych, jednowymiarowych tablic mamy szereg funkcji pozwalających na posortowanie jej. Dzielą się one na te które sortują po kluczach lub wartościach lub też definiują kierunek w którym elementy zostaną posortowane. Takimi funkcjami są m. in. asort, ksort, arsort, krsort itd.

<?php



class Foo

{

    public $id;

    public function __construct($id)  {

        $this->id = $id;

    }

}


$arr = [new Foo(17), new Foo(18),  new Foo(16)];


array_multisort(

    array_column($arr, 'id'), SORT_ASC, $arr

);


print_r($arr);

W powyższym kodzie znajduje się prosta klasa Foo która posiada jedno publiczne pole $id inicjowane w konstruktorze. Wykorzystanie funkcji array_column pozwala zwrócić wartości z poszczególnych pól obiektu (PHP 7.0) lub kolumn w przypadku tablic asocjacyjnych. W połączeniu z funkcją array_multisort pomoże nam posortować tablice instancji Foo.

Array

(

    [0] => Foo Object

        (

            [id] => 16

        )

    [1] => Foo Object

        (

            [id] => 17

        )

    [2] => Foo Object

        (

            [id] => 18

        )

)

Alternatywną metodą do posortowania tablicy obiektów może być ta przedstawiona poniżej z użyciem funkcji uasort. Rezultat będzie taki sam jak dla kombinacji funkcji array_column i array_multisort.

uasort ($arr, function (Foo $a, Foo $b) {

    return $a->id > $b->id;

});

Porównywanie tablicy

Sprawdzanie czy dwie tablice składają się tylko i wyłącznie z tych samych elementów w pewnych przypadkach może okazać się kłopotliwe.

<?php


$foo = ['A', 'B'];

$bar = ['B', 'A'];


var_dump($foo === $bar);

Te tablice zawierają te same elementy, ale powyższy kod zwróci false z tego względu, że operator porównania w przypadku tablic sprawdza również zgodność kluczy. Możemy oczywiście te dwie tablice najpierw posortować, ale ciekawszym i w niektórych przypadkach lepszym rozwiązaniem jest porównanie różnic tablic przez wywołanie funkcji array_diff.

array_diff($foo, $bar) === array_diff($bar, $foo)

Znajdowanie maksymalnej wartości pola obiektu

Mamy sytuację w której chcemy poznać maksymalną wartość danego pola obiektu, np. najwyższą cenę produktu.

<?php


class Product

{

    private $price;

    public function __construct($price) {

        $this->price = $price;

    }

    public function getPrice()  {

        return $this->price;

    }

}


$arr = [

    new Product(2.00),

    new Product(3.45),

    new Product(1.23)

];


$max = max(array_map(function(Product $product) {

    return $product->getPrice();

}, $arr));

W rezultacie otrzymamy oczywiście 3.45. W powyższym przykładzie wykorzystaliśmy funkcję array_map która pozwoliła nam z każdego produktu wyciągnąć cenę. Jeśli posłużylibyśmy się tutaj obiektami z publicznymi właściwościami (lub tablicą) moglibyśmy wykorzystać wcześniej poznaną funkcję array_column.

Autorem tekstu jest Jakub Sładek.

Code Review

Code review to jeden z najlepszych sposobów, aby pomóc zespołowi upewnić się, że pisze najlepszy możliwy kod. Daje możliwość nie tylko wyłapania błędów, ale również przedyskutować wprowadzane zmiany, dając wszystkim członkom zespołu możliwość zapoznania się z nowymi funkcjonalnościami.

Z jednej strony code review wydawać się może zbędnym wysiłkiem w sytuacji gdy aplikacja jest pokryta testami TDD. W tej sytuacji możemy bez większych przeszkód zmieniać kod lub dopisać jego nowe elementy wraz z kolejnymi testami, które obrazują jak oczekujemy, aby kod działał. Uruchamiamy testy i w jednej chwili wiemy czy aplikacja działa lub nie.

Z drugiej strony jako autor kodu zawsze stawiam pewne założenia dotyczące sposobu działania systemu i czego użytkownik potrzebuje, dlatego przegląd kodu przez drugiego programistę daje szanse wyłapania błędów logicznych lub problemów strukturalnych, których komputer nie może zobaczyć. Dodatkowo osoba wykonująca code review zaznajamia się z fragmentem systemu, nad którym może aktualnie nie pracować i w sytuacji kryzysowej dokonać niezbędnych poprawek lub zmian.

W niniejszym artykule chciałbym podzielić się paroma praktykami, w którym code review przebiega płynnie niezależnie po której stronie procesu oceny stoimy.

1. Nie karz. Celem code review jest powstanie najlepszego możliwego kodu. Staramy się być pomocni i upewniajmy się, że komentarze nie wydają się oskarżycielskie.

2. Zadawaj pytania. Należy zadawać pytania odnośnie wszystkiego, czego nie mamy pewności. Kod jest jasny dla autora, niemniej dla recenzenta duże partie kodu nie zawsze bywają oczywiste. Dzięki pytaniom i odpowiedziom uzyskujemy większą klarowność nad daną funkcjonalnością. W zespole pracujemy razem. Kompromis wymaga rozmowy i zrozumienia.

3. Nikt nie jest właścicielem kodu. Staramy się unikać sytuacji „własności kodu”. Gdy któryś z członków zespołu przypisuje własność kodu, wówczas staje się mniej podatny na sugestie i zmiany sądząc, że pierwotna wersja jest doskonała.

4. Przekaż pełną recenzję. W trakcie code review przeglądamy cały kod, a nie skupiamy się wyłącznie na fragmentach lub klasach. Jeśli czegoś nie wiemy… patrz pkt. 2. W przeciwnym wypadku narażamy autora na wykonywanie bezproduktywnych poprawek tylko dlatego, że recenzent nie przejrzał całego kodu.

Reasumując, staramy się nie tylko patrzeć na składnie kodu, ale także na jego architekturę. Przekazujemy własne opinie i sugestie dochodząc wspólnie do jak najlepszych rozwiązań.

W internecie oczywiście jest wiele tipów i praktyk code review. Najlepiej znanym jest artykuł na gitlabie: https://gitlab.com/help/development/code_review.md niemniej mam nadzieje, że i nasze drobne sugestie przekonają czytelnika o pozytywnych aspektach niniejszego procesu.

Autorem tekstu jest Marek Rode.

Ferie z programowaniem

W połowie lutego, rozpocznie się zimowy kurs ,,Ferie z programowaniem”, organizowany przez i-systems. Przez cały tydzień uczestnicy będą zdobywali i szlifowali wiedzę z wybranych przez siebie tematów. Kurs skierowany jest do osób z podstawową wiedzą z zakresu programowania. Stawiamy na osoby ambitne, pracowite i takie, dla których kodzenie to pasja.

Termin i miejsce

Kurs odbędzie się w dniach: 12.02 – 16.02.2018 w Gliwicach, przy ulicy Bojkowskiej 47. Zajęcia będą trwały od godziny 8:00 do 16:00. Zapowiada się 5 intensywnych dni. 😉

Tematy do wyboru

  • Optymalizacja, wyszukiwanie wąskich gardeł w kodzie PHP
  • MySQL
  • Wzorce projektowe
  • Testy jednostkowe
  • Zarządzanie wersjami / composer
  • Debugging
  • Webservice’s
  • SPL
  • Domain Driven Design
  • Programowanie zorientowane obiektowo
  • SOLID
  • Hello world
  • IDE
  • SOLR
  • Testy Behat
  • CQRS
  • ES – Event Sourcing

Sylwetki prowadzących kurs:

Adam – programista PHP, od 3 lat pracujący komercyjnie, specjalizuje się w optymalizacji aplikacji

Łukasz – programista z 8-letnim stażem, specjalista w dziedzinie wzorców projektowych oraz testowania oprogramowania

Korneliusz – programista PHP z 10-letnim stażem, specjalizuje się w algorytmach, optymalizacji oraz integracji sklepów

Konrad – szef działu IT w i-systems, od 3 lat zarządza zespołem i projektami w IT

Tomek – programista PHP oraz Team Leader, w zawodzie od 6 lat, specjalizuje się w analizie i penetracji kodu oraz integracji z systemami zewnętrznymi.

Patrycja – testerka, pracuje z językiem PHP, od pół roku związana z testami automatycznymi, frameworkiem Behat oraz procesami TDD i BDD.

Korzyści z udziału w kursie:

  • Możliwość pracy w i-systems dla najlepszych uczestników
  • Zdobycie praktycznych umiejętności w zakresie podstaw programowania
  • Uzyskanie certyfikatu ukończenia kursu
  • Poznanie specyfiki pracy w IT
  • Wymiana wiedzy z innymi uczestnikami kursu
  • Nauka w kameralnym gronie

Rekrutacja

Poniżej znajduje się kilka kroków, które należy wykonać, aby wziąć udział w rekrutacji. Oto one:

  1. Przeczytaj ogłoszenie z ofertą kursu: http://bit.ly/2CRFUU2
  2. Wyślij poprzez formularz swoje CV* oraz list motywacyjny, w którym opiszesz/zakodujesz/narysujesz, dlaczego to właśnie Ty powinieneś wziąć udział w kursie.
  3. Termin nadsyłania aplikacji mija 21.01.2018 r. Wszystkie zgłoszenia po terminie, nie zostaną rozpatrzone. Grunt to trzymać się dedlajnu. 😉
  4. Czekaj na nasz telefon w sprawie umówienia się na spotkanie (6 lutego zostanie wybrana ostateczna lista uczestników, których poinformujemy o szczegółach).
  5. Wybierz 10 interesujących Cię tematów z listy zaproponowanych.
  6. Przyjdź na kurs 12 lutego, o godzinie 8:00 do siedziby i-systems.

*Osoby, które brały udział w kursie “Wakacje z programowaniem” nie mogą wziąć udziału w procesie rekrutacji do tego kursu.

Ferie z programowaniem to idealna okazja i szansa dla młodych osób, które swoją przyszłość wiążą z branżą IT. Uczestnictwo w kursie niesie za sobą wiele korzyści, dlatego zachęcamy do wzięcia udziału w procesie rekrutacji.

Zdarzenia i delegaty w języku C#

Dzisiejszy wpis będzie poświęcony dwóm bardzo interesującym mechanizmom języka C# – zdarzenia i delegaty.

Delegaty

Delegat w rozumieniu języka programowania to wskaźnik na funkcję – pojęcie znane z takich języków jak C++ czy Java. Jednak w odróżnieniu od innych języków w C#, mechanizm ten został uproszczony i łatwiejszy w użyciu. Delegaty umożliwiają późne wiązanie, czyli decyzja o utworzeniu obiektu zapada w trakcie działania programu. Przeanalizujmy przykład takiego delegata.

1

Tworzenie delegata odbywa się jak tworzenie zwykłej metody, przy czym należy zaznaczyć, że tworzymy delegat za pomocą słowa kluczowego delegate. Tworzone delegaty nie mają implementacji. Sygnaturę delegata określają argumenty jakie przyjmuje oraz zwracany typ. Używając delegatów należy pamiętać, że delegaty to po prostu nowe typy danych, z tym że tworząc zmienne ich typów można przypisać do nich metody o odpowiadającej im sygnaturze. Poniżej przykład użycia delegata.

Deklaracja metod o takiej samej sygnaturze jak sygnatura naszego delegata:

2

Utworzenie zmiennych delegata wskazujących na wcześniej zadeklarowane metody:

4

Do delegata można przypisać więcej niż jedną metodę. Dzięki temu w momencie wywołania delegata zostaną wywołane wszystkie metody przypisane do niego:

5

Zdarzenia

Zdarzenia to elementy wykorzystywane bardzo często, są to akcje wykonywane w momencie zaistnienia pewnej sytuacji, np. ruch myszką, kliknięcie na przycisk, itp.. Zdarzenia w języku C# opierają się na wcześniej opisanych delegatach.

Aby zadeklarować zdarzenie należy użyć słówka kluczowego event:

6

W tym przypadku do zmiennej Click będzie można przypisywać metody zgodne z sygnaturą delegata EventHandler, nic nie stoi na przeszkodzie, aby użyć w tym miejscu własnego delegata, pamiętając o tym aby przypisywać do zmiennej metody (może ich być więcej niż jedna) o takiej samej sygnaturze jak nasz delegat. W momencie zajścia określonego zdarzenia (np. kliknięcie na przycisk) wywołane zostaną wszystkie metody przypisane do tego zdarzenia. Przypisanie metody do zdarzenia odbywa się dokładnie tak samo jak przypisanie metody do delegata:

7
8

Podsumowanie

Delegaty i zdarzenia to bardzo użyteczne mechanizmy języka C#. Za ich pomocą można, przede wszystkim zwiększyć czytelność kodu, ponieważ zmniejszamy ilość instrukcji warunkowych, przez co kod staje się bardziej zrozumiały. Pisząc aplikacje desktopowe (WinForms, WPF) delegaty i zdarzenia będą naszymi nieodzownymi towarzyszami, gdyż pisanie aplikacji okienkowych opiera się głównie na zdarzeniach.

Autorem tekstu jest Mateusz Łysień.

MyISAM vs InnoDB

MyISAM vs InnoDB

Podczas pierwszych etapów pisania aplikacji opartej o MySQL możemy zmierzyć się z dylematem: jaki mechanizm składowania danych użyć dla poszczególnych tabel? Postaram się w poniższym wpisie przybliżyć i porównać dwa, z powszechnie stosowanych – MyISAM oraz InnoDB.

MyISAM

MyISAM jest mechanizmem składowania danych, zaprojektowany z myślą o pobieraniu i dodawaniu danych. Dzięki temu, że każda krotka wskazuje na rekord w pliku i wskaźnik do niej jest przesuwany względem jego początku, wprowadzanie nowych danych jest bardzo wydajne (nowy rekord jest dodawany na sam koniec pliku).

Uaktualnianie danych, jak i usuwanie, jest dużo bardziej problematyczne z natury mechanizmu, dzięki któremu odczyt jest tak wydajny tj. podczas usuwania krotki wszystkie wskaźniki „za” danym rekordem muszą zostać zaktualizowane. Tak samo podczas aktualizacji rekordu – gdyż jego efektem przeważnie jest zmiana wielkości.

InnoDB

Mechanizm składowania danych jakim jest InnoDB jest dużo bardziej skomplikowany i mógłby być materiałem na kolejny wpis – dlatego, też przedstawię tylko jego rewolucyjne względem MyISAM właściwości.

Pierwszym, bez wątpienia bardzo istotnym usprawnieniem, jest możliwość stosowania kluczy obcych w tabelach, zapewniających integralność między tabelami, co w połączniu z utworzeniem indeksu dla danego klucza usprawnia wydajność tabeli.

Drugim rewolucyjnym usprawnieniem względem MyISAM jest możliwość korzystania z transakcji – umożliwia to wydzielenie całości ze zbioru operacji na bazie danych, dzięki czemu, w razie niepowodzenia którejkolwiek z danych operacji, mamy możliwość odwrócenia zmian.

MyISAM vs InnoDB

Poniżej załączam zestawienie cech wyżej przedstawionych silników:

1

Podsumowanie

Przy podejmowaniu decyzji podczas zakładania tabeli warto dokładnie przeanalizować zadania, jakie nasza tabela ma spełniać, dzięki temu, być może w przyszłości zaoszczędzimy trochę czasu na optymalizacji (a raczej na jej braku).

Autorem tekstu jest Michał Gaj.

Lokalne środowisko z użyciem Vagranta

W jednym z poprzednich artykułów mój imiennik, czyli Łukasz wspominał jak uprościć sobie pracę z wirtualnymi hostami.
Ja zaprezentuję kolejny sposób, a mianowicie Vagrant. Aby móc skonfigurować środowisko lokalne za pomocą Vagranta będziemy potrzebować, tutaj zaskoczę wszystkich, Vagranta oraz VirtualBoxa.

W/w rzeczy można pobrać tutaj:
– Vagrant: https://www.vagrantup.com/downloads.html
– VirtualBox: https://www.virtualbox.org/wiki/Downloads

Po zainstalowaniu można przejść do konfiguracji środowiska lokalnego. Pierwsze, co należy zrobić to dodać box, czyli środowisko na jakim chcemy pracować. Robimy to za pomocą komendy:

vagrant box add precise64

http://files.vagrantup.com/precise64.box.

Naszym środowiskiem w tym przypadku będzie Ubunty 12.04. Kolejnym krokiem jest inicjalizacja na w/w boxie poprzez komendę:

vagran init precise64

1

Polecenie tworzy plik Vagrantfile, gdzie możemy zamieścić całą konfigurację. W moim przypadku zmienię IP środowiska na 192.168.68.8.

1

Dostępne boxy można znaleźć tutaj: https://app.vagrantup.com/boxes/search. Odpalamy:

vagrant up

dzięki czemu uruchamiamy całe środowisko.

1

Następnie komendą:

vagrant ssh

łączymy się z wirtualna maszyną po ssh.

1

Po połączeniu możemy zainstalować wszystko co potrzebujemy do dalszej pracy. Robimy to za pomocą:

sudo apt-get install

aby zobaczyć czy lokalnie wszystko śmiga zainstaluję apache2:

sudo apt-get install apache2

1

Pozostaje przejść do przeglądarki i zobaczyć czy wszystko jest ok.

1

Autorem tekstu jest Łukasz Cieślik.

Rzecz o tworzeniu stringów w PHP.

PHP od początku oferuje wiele różnych sposobów na wygenerowanie takiego samego stringa. Ale który z nich jest najszybszy? W tym celu przeprowadziłem serię 9 testów – każdy test wykonywał się w pętli o 10 000 000 iteracji i budował string w inny sposób:

1. $a = sprintf("ala ma kota numer %d", $i);
2. $a = "ala ma kota numer " . $i;
3. $a = 'ala ma kota numer ' . $i;
4. $a = "ala ma kota numer $i";
5. $a = sprintf("ala ma kota numer %d%c%c%c%c%c%c%c%c%c%c", $i, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL);
6. $a = "ala ma kota numer " . $i . "\n\n\n\n\n\n\n\n\n\n";
7. $a = 'ala ma kota numer ' . $i . "\n\n\n\n\n\n\n\n\n\n";
8. $a = "ala ma kota numer $i\n\n\n\n\n\n\n\n\n\n";
9. $a = sprintf("ala ma kota numer %d%s%s%s%s%s%s%s%s%s%s", $i, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL, PHP_EOL);

Oczywiście nie wyczerpałem wszystkich możliwych kombinacji, ale przypuszczam że dość dobrze przedstawiłem istotę wszystkich możliwych do wykorzystania technik. Testy przeprowadziłem na wszystkich posiadanych wersjach PHP: 5.3.9; 5.4.45; 5.5.38; 5.6.31; 7.0.22; 7.1.8; 7.2.0beta2. Wszystkie wersje mają praktycznie takie same konfiguracje, zero dodatkowych modułów – okazało się np. że xdebug potrafi nieźle spowolnić interpreter.

Porównanie czasu wykonywania poszczególnych testów jest następujące:

1

Można by się spodziewać, że skoro fukncja sprintf() jest praktycznie taka sama jest w C (w którym został napisany PHP) to będzie proste mapowanie i będzie ona najszybsza, a tymczasem jest najwolniejsza; a im więcej argumentów (testy 5 i 9) tym czas wykonania znacznie większy… Masakra.
Między testami wykorzystującymi składnię typową dla PHP nie ma większych różnic, choć jestem mocno zaskoczony faktem, że składnia „asd $zmienna” jest najszybsza dla PHP7.x – jednocześnie jest to dla mnie konstrukcja najbardziej podatna na możliwość popełnienia błędu przez programistę.

Porównanie wydajności w zależności od wersji PHP nie było zaskoczeniem:

iqsi-screenshot (14)

PHP 5.3 najwolniej, PHP 7.2 najszybciej. Widać oczywiście poprawę z wersji na wersję i gwałtowne przyspieszenie dla wersji 7.x w związku z nowym silnikiem.

Jeśli ktoś jest zainteresowany dokładnymi danymi to mogę przesłać tabelkę w excelu.

Autorem tekstu jest Łukasz Bugaj.