O jakości firmy programistycznej decyduje proces wytwarzania kodu, a nie lista technologii w ofercie. Strona opisuje, jak pracuje zespół wytwórczy: kto odpowiada za jaki etap, jak wygląda historia zmian w repozytorium Git, jak działa przegląd kodu, jakie testy pisze się w C# i jak zmiany trafiają do bazy T-SQL. Opis dotyczy mechanizmów ogólnych. Służy jako lista pytań do zespołu, a nie jako regulamin pracy któregokolwiek konkretnego dostawcy.

Firma programistyczna Poznań i zespół wytwórczy

Zespół, który buduje aplikację biznesową, dzieli pracę według odpowiedzialności za wynik. Podział ma znaczenie praktyczne: każda rola zostawia po sobie artefakt, który zamawiający może zobaczyć i ocenić. Zgodnie z opisem SoftwareStudio w zespole projektowym pracują analitycy i programiści, a także testerzy oraz specjaliści od wdrożeń, którzy odpowiadają za cały cykl życia aplikacji, od pierwszej rozmowy po wsparcie po uruchomieniu.

RolaWynik pracyCo widzi zamawiający
AnalitykSpecyfikacja funkcjonalna oraz opis procesów i integracjiDokument do akceptacji przed rozpoczęciem programowania
ProgramistaKod aplikacji i skrypty bazy danychWersje w środowisku testowym po kolejnych iteracjach
TesterScenariusze testowe i raport z wykonaniaLista usterek z priorytetami przed odbiorem
WdrożeniowiecInstalacja i migracja danych, a także szkolenieUruchomienie produkcyjne i instrukcje dla użytkowników

Warstwa technologiczna SoftwareStudio opiera się na platformie .NET i bazach danych SQL Server. Aplikacje webowe są hostowane w chmurze prywatnej działającej na platformie VMware, a programiści zajmują się też integracją nowego oprogramowania z systemami klasy ERP oraz z rozwiązaniami WMS i CRM, które firma już używa. Firma pomaga również przy opisie założeń rozwiązania, na przykład w specyfikacji do wniosków o dofinansowanie unijne. Ogólne podejście do budowy aplikacji dla przedsiębiorstw opisuje strona o programowaniu aplikacji biznesowych, a rolę zewnętrznego wykonawcy w porównaniu z gotowym produktem strona o producencie oprogramowania.

Reguła: każdy etap prac kończy się artefaktem, który da się sprawdzić bez rozmowy z autorem: dokumentem, wersją do przetestowania albo raportem.

Git - gałęzie i znaczniki wersji

Repozytorium Git przechowuje kod razem z historią każdej zmiany. Standardowy układ pracy wygląda tak: programista tworzy gałąź na jedno zadanie, zapisuje zmiany w niewielkich commitach z opisem, wypycha gałąź na serwer i zgłasza ją do przeglądu. Dopiero po zaakceptowaniu zmiana trafia do gałęzi głównej, z której powstaje wersja wdrażana. Poniższe polecenia pokazują ten cykl od strony programisty.

git switch -c zmiana/rezerwacja-stanu
git add .
git commit -m "Rezerwacja: walidacja ilości względem stanu dostępnego"
git push origin zmiana/rezerwacja-stanu
git tag -a v2.4.0 -m "Wydanie 2.4.0"

Ostatnie polecenie zapisuje znacznik wersji na wskazanym commicie. Dzięki niemu na pytanie „jaki kod działa dziś u klienta” odpowiada się jednym poleceniem, a błąd zgłoszony w wersji 2.4.0 można odtworzyć na dokładnie tym samym kodzie. Pełny opis poleceń i modeli gałęzi zawiera oficjalna książka Pro Git w polskiej wersji.

ElementDo czego służySygnał ostrzegawczy
Gałąź na zadanieIzoluje pracę nad jedną zmianą od reszty koduWszyscy zapisują bezpośrednio do jednej gałęzi
Opis commitaWyjaśnia, dlaczego zmieniono kod, a nie tylko coOpisy typu „poprawki” lub „zmiany”
Znacznik wersjiWiąże wdrożenie z konkretnym stanem koduBrak znaczników, wersji nie da się odtworzyć
Ochrona gałęzi głównejWymusza przegląd i testy przed scaleniemBezpośrednie zapisy na gałąź produkcyjną

Przegląd kodu jako kontrola zmiany

Przegląd kodu polega na tym, że druga osoba czyta zmianę przed jej scaleniem i zostawia uwagi. Nie zastępuje testów, tylko wychwytuje błędy, których testy nie przewidziały: pomyłkę w warunku granicznym, brak sprawdzenia uprawnień, zapytanie, które przy większej tabeli zatrzyma system. Sprawdza się tym lepiej, im mniejsza jest zmiana, bo dużą zmianę recenzent przegląda pobieżnie. Dlatego zadania dzieli się tak, żeby jedna gałąź dotyczyła jednej sprawy i dawała się przeczytać w jednym posiedzeniu, a uwagi recenzenta trafiały do autora zanim ten zacznie kolejne zadanie.

  • Poprawność logiki - czy zmiana robi to, co opisuje zadanie, także dla wartości granicznych.
  • Bezpieczeństwo - czy zapytania są parametryzowane, a uprawnienia sprawdzane po stronie serwera.
  • Wydajność - czy zapytanie korzysta z indeksu i nie wykonuje w aplikacji pętli po pojedynczych wierszach.
  • Czytelność - czy nazwy i podział na metody pozwolą zrozumieć kod osobie, która nie jest jego autorem.
Dwie osoby przy stole z laptopem i wydrukami, wspólnie przeglądają dokumentację
Przegląd kodu wykonuje druga osoba z zespołu, czytając zmianę razem z opisem zadania, do którego się odnosi.

Najczęstszą uwagą w przeglądach aplikacji bazodanowych jest sklejanie tekstu zapytania z danymi od użytkownika. Wersja z parametrem chroni przed wstrzyknięciem SQL i pozwala serwerowi ponownie użyć planu wykonania zapytania. Przykład w C# pokazuje wersję poprawną, przy założeniu, że połączenie conn jest już otwarte.

using var cmd = new SqlCommand(
    "SELECT IdZlecenia, Status FROM dbo.Zlecenie WHERE Numer = @numer", conn);
cmd.Parameters.Add("@numer", SqlDbType.NVarChar, 30).Value = numer;
using var reader = cmd.ExecuteReader();

Zasady pracy nad zmianą w krótkich iteracjach, z regularnym pokazywaniem postępu, opisuje strona o zwinnym zarządzaniu projektami IT na zamówienie. Wynikiem przeglądu jest decyzja o zatwierdzeniu zmiany albo o jej poprawie, a obie zostawiają ślad w systemie repozytorium.

Testy jednostkowe i integracyjne w C#

Test jednostkowy sprawdza pojedynczą regułę bez bazy danych i sieci, więc wykonuje się w milisekundach. Test integracyjny uruchamia kod na prawdziwym SQL Server i sprawdza, czy zapytania i transakcje współpracują z mapowaniem danych. Oba poziomy uzupełniają się: pierwszy pilnuje logiki, drugi połączenia między aplikacją a bazą. Dokumentacja platformy .NET opisuje testowanie w .NET oraz dostępne biblioteki.

Przykład poniżej pokazuje regułę stanu magazynowego: nie można zarezerwować więcej, niż jest dostępne, a nieudana rezerwacja nie zmienia stanu. Klasy mają charakter poglądowy i nie odwzorowują schematu żadnego produktu.

public record WynikRezerwacji(bool Powodzenie);

public class StanMagazynowy
{
    public decimal Dostepne { get; private set; }
    public StanMagazynowy(decimal dostepne) => Dostepne = dostepne;

    public WynikRezerwacji Zarezerwuj(decimal ilosc)
    {
        if (ilosc <= 0 || ilosc > Dostepne) return new WynikRezerwacji(false);
        Dostepne -= ilosc;
        return new WynikRezerwacji(true);
    }
}

public class RezerwacjaTests
{
    [Fact]
    public void Rezerwacja_ponad_stan_nie_zmienia_stanu()
    {
        var stan = new StanMagazynowy(5m);
        var wynik = stan.Zarezerwuj(8m);
        Assert.False(wynik.Powodzenie);
        Assert.Equal(5m, stan.Dostepne);
    }
}
Poziom testuCo sprawdzaKiedy się go uruchamia
JednostkowyPojedyncze reguły i obliczenia bez zależności zewnętrznychPrzy każdej zmianie, na komputerze programisty i na serwerze
IntegracyjnyZapytania i procedury na testowej bazie SQL Server, w tym transakcjePrzed scaleniem zmiany oraz przed wydaniem
AkceptacyjnyScenariusze biznesowe według specyfikacjiNa środowisku testowym przed odbiorem

Proces powstawania aplikacji od analizy po odbiór opisuje strona o procesie tworzenia aplikacji biznesowych. Zamawiający może poprosić o raport z ostatniego przebiegu testów jako element odbioru, a nie tylko o ich deklarację.

T-SQL i migracje bazy danych

Zmiana w aplikacji rzadko obywa się bez zmiany w bazie. Każda zmiana schematu, czyli nowa kolumna czy indeks, musi trafić na każde środowisko w tej samej kolejności, dlatego skrypty T-SQL trzyma się w repozytorium obok kodu i numeruje. Skrypt migracji powinien być idempotentny: uruchomiony drugi raz nie psuje niczego, tylko sprawdza, czy zmiana jest już zastosowana. Rejestr wersji schematu wystarcza do tego jedna mała tabela.

IF OBJECT_ID('dbo.WersjaSchematu') IS NULL
    CREATE TABLE dbo.WersjaSchematu (
        Wersja int NOT NULL PRIMARY KEY,
        Zastosowano datetime2 NOT NULL DEFAULT SYSUTCDATETIME()
    );
GO

IF NOT EXISTS (SELECT 1 FROM dbo.WersjaSchematu WHERE Wersja = 12)
BEGIN
    ALTER TABLE dbo.Zlecenie ADD Uwagi nvarchar(500) NULL;
    INSERT dbo.WersjaSchematu (Wersja) VALUES (12);
END
GO
Ilustracja bazy danych: stosy walców z danymi, wykresy, ikony chmury i telefonu połączone liniami
Baza danych jako wspólny element aplikacji webowej i mobilnej oraz raportów: zmiana schematu dotyka ich wszystkich.

Logikę, która musi wykonać się w całości albo wcale, zamyka się w transakcji. Procedura poniżej zmienia status zlecenia i zapisuje wpis w historii, przy czym status wolno tylko zwiększyć, więc równoległe wywołanie nie nadpisze nowszego stanu starszym. Ustawienie SET XACT_ABORT ON powoduje wycofanie całej transakcji przy pierwszym błędzie wykonania. Składnię wyjaśnia dokumentacja SET XACT_ABORT w Microsoft Learn. Instrukcja CREATE OR ALTER jest dostępna od SQL Server 2016 z dodatkiem SP1.

CREATE OR ALTER PROCEDURE dbo.ZmienStatusZlecenia
    @IdZlecenia int,
    @Status tinyint
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;

    BEGIN TRANSACTION;
        UPDATE dbo.Zlecenie
        SET Status = @Status
        WHERE IdZlecenia = @IdZlecenia AND Status < @Status;

        IF @@ROWCOUNT = 1
            INSERT dbo.ZlecenieHistoria (IdZlecenia, Status, Kiedy)
            VALUES (@IdZlecenia, @Status, SYSUTCDATETIME());
    COMMIT TRANSACTION;
END

Reguła: skrypt bazy trafia do repozytorium razem ze zmianą kodu, która go wymaga, i przechodzi ten sam przegląd.

Szerzej o projektowaniu tabel i procedur oraz o doborze indeksów pisze strona o programowaniu baz danych. Wymiana danych z innymi systemami, z własną transakcją po każdej stronie, opisano w tekście o integracji WMS z ERP przez REST i kolejki.

Warstwa przeglądarkowa - jQuery i AJAX

Aplikacje webowe SoftwareStudio budowane są w oparciu o jQuery i HTML5, a użytkownik pracuje w przeglądarce bez instalacji na stanowisku. Dla zespołu wytwórczego oznacza to jedno ograniczenie i jedną zaletę. Ograniczenie: przeglądarka jest środowiskiem niezaufanym, więc każda reguła biznesowa i każde sprawdzenie uprawnień musi istnieć także na serwerze. Zaleta: nowa wersja trafia do wszystkich użytkowników w chwili wdrożenia po stronie serwera, bez aktualizacji na komputerach.

$.ajax({
    url: '/api/zlecenia/status',
    method: 'POST',
    contentType: 'application/json',
    data: JSON.stringify({ id: 42, status: 3 })
})
.done(function () { odswiezListe(); })
.fail(function (xhr) {
    if (xhr.status === 409) { pokazKomunikat('Status został zmieniony przez innego użytkownika.'); }
    else { pokazKomunikat('Nie udało się zapisać zmiany.'); }
});

Kod odpowiedzi 409 oznacza konflikt: serwer odrzucił zmianę, bo stan zlecenia jest już nowszy, dokładnie tak jak w procedurze z poprzedniej sekcji. Interfejs pokazuje użytkownikowi komunikat i odświeża listę, zamiast po cichu nadpisywać cudze dane. Ten sam mechanizm, nazywany optymistyczną kontrolą współbieżności, obowiązuje w każdym miejscu, gdzie dwie osoby mogą edytować ten sam rekord. Przyciski, które wysyłają żądanie, blokuje się do czasu odpowiedzi, żeby dwukrotne kliknięcie nie wywołało operacji dwa razy. Podstawy takiej architektury omawia strona o aplikacji webowej.

Środowiska i wdrożenie zmian

Kod przechodzi przez środowisko deweloperskie i testowe, zanim trafi na produkcyjne. Na testowym zamawiający akceptuje zmiany na kopii danych, produkcja dostaje wyłącznie wersję z oznaczonego znacznikiem commita. Wdrożenie obejmuje kopię zapasową bazy przed migracją, uruchomienie skryptów w ustalonej kolejności, test dymny najważniejszych funkcji i plan powrotu do poprzedniego znacznika. Bez planu powrotu każde wdrożenie jest jednokierunkowe.

Korytarz serwerowni z rzędami szaf serwerowych po obu stronach i podświetloną podłogą
Środowisko produkcyjne w chmurze prywatnej: wdrożenie zaczyna się od kopii bazy i kończy testem funkcji po uruchomieniu nowej wersji.

Poprawka pilna na podstawie znacznika wersji

Błąd produkcyjny naprawia się na kodzie, który faktycznie działa u klienta, a nie na aktualnej gałęzi głównej, bo ta zawiera już zmiany jeszcze niewydane. Programista tworzy gałąź poprawkową z tagu wdrożonej wersji i poprawia usterkę. Zmiana przechodzi przegląd oraz testy, po czym dostaje nowy tag. Zmianę wprowadza się następnie także do gałęzi głównej, żeby błąd nie wrócił w kolejnym wydaniu.

git switch -c poprawka/2.4.1 v2.4.0
git commit -am "Naprawa: blokada zlecenia przy jednoczesnej zmianie statusu"
git tag -a v2.4.1 -m "Poprawka 2.4.1"

Czas, w jakim taka poprawka powstaje i trafia na produkcję, zależy od priorytetu zgłoszenia i zapisów umowy. W umowie SLA usterki blokujące pracę odróżnia się od drobnych błędów i każdej z tych grup przypisuje inny czas reakcji.

Po uruchomieniu produkcyjnym zespół zapewnia wsparcie techniczne w ramach umowy SLA, czyli reaguje na zgłoszenia w ustalonym czasie. Jak czytać takie zapisy i co sprawdzić w umowie przed podpisaniem, opisuje strona o tym, jak ocenić firmę informatyczną z Poznania. Szerszy opis wytwarzania oprogramowania w tym mieście przedstawia strona o software house w Poznaniu.

Test dojrzałości procesu: poprosić zespół o pokazanie historii jednej niedawnej zmiany od zadania przez przegląd i testy aż do znacznika wersji, w której trafiła na produkcję. Firma z uporządkowanym procesem robi to w kilka minut.

Taki test da się wykonać na spotkaniu ofertowym, bez dostępu do kodu, a jego wynik mówi o zespole więcej niż lista certyfikatów czy technologii. Więcej o usługach programistycznych SoftwareStudio znajduje się na stronie usług SoftwareStudio.