Temat: Redukowanie sprzężeń czasowych pomiędzy funkcjami

Ze sprzężeniem czasowym (temporal coupling) pomiędzy funkcjami mamy do czynienia wówczas, gdy do poprawnego działania jednej funkcji, konieczne jest wcześniejsze wywołanie jakiejś innej funkcji.

Sytuacja taka jest bardzo niekorzystna, ponieważ zwiększa tzw. kruchość systemu, czyli podatność na awarię jednego modułu, po dokonaniu zmiany w innym. A to prowadzi do zwiększonego lęku przed wprowadzaniem zmian i ulepszeń, "żeby czegoś nie zepsuć".

Dlatego powinniśmy mieć to na uwadze, minimalizować takie sprzężenia czasowe i dbać o to, aby funkcje były maksymalnie samowystarczalne.

Dobrym przykładem może być losowanie liczb.

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

void inicjalizuj_generator() {
    srand((unsigned)time(NULL));
}

int losuj(int zakres) {
    return rand() % zakres;
}

int main(void) {
    inicjalizuj_generator();
    printf("%d\n", losuj(10));
    printf("%d\n", losuj(10));
    return 0;
}

W powyższym przykładzie poprawne działanie funkcji losuj() uzależnione jest od wcześniejszego wywołania funkcji inicjalizuj_generator(), między tymi funkcjami zachodzi więc sprzężenie (bez inicjalizacji, przy każdym uruchomieniu programu losowane były by te same liczby).

Aby zniwelować to sprzężenie, funkcja losuj() powinna sama zadbać o wszystko co jest jej potrzebne do poprawnego działania, czyli również o zainicjalizowanie generatora.

Jeżeli jednak wywołamy funkcję inicjalizuj_generator() w funkcji losuj():

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

void inicjalizuj_generator() {
    srand((unsigned)time(NULL));
}

int losuj(int zakres) {
    inicjalizuj_generator();
    return rand() % zakres;
}

int main(void) {
    printf("%d\n", losuj(10));
    printf("%d\n", losuj(10));
    return 0;
}

to program nie będzie działał poprawnie, gdyż ciągłe inicjalizacje spowodują, że losowana będzie taka sama liczba.

Rozwiązaniem jest zapewnienie, że inicjalizacja generatora liczb pseudolosowych zostanie wykonana tylko raz w ciągu całego działania programu. Do tego celu możemy wykorzystać zmienne statyczne (czyli zmienne, które pamiętają swoje wartości pomiędzy kolejnymi uruchomieniami funkcji).

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

void inicjalizuj_generator() {
    static int zainicjalizowany = 0;
    if (!zainicjalizowany) {
        srand((unsigned)time(NULL));
        zainicjalizowany = 1;
    }
}

int losuj(int zakres) {
    inicjalizuj_generator();
    return rand() % zakres;
}

int main(void) {
    printf("%d\n", losuj(10));
    printf("%d\n", losuj(10));
    return 0;
}

Takie rozwiązanie sprawia, że funkcja losuj() sama posiada wszystko co potrzeba do jej prawidłowego działania, nie jest uzależniona od żadnych zewnętrznych "magicznych" czynników.

Natomiast można by się teraz zastanowić, czy funkcja inicjalizuj_generator() po zmianach nie wymaga nadania lepszej nazwy.

Rozwiązanie, które zapewnia nam, że coś zostanie wykonane tylko raz, nosi nazwę singleton.