czy zagnieżdżona logika warunków ma sens?

czy zagnieżdżona logika warunków ma sens?
ZK
  • Rejestracja: dni
  • Ostatnio: dni
0

jak w tytule i oto kawałek kodu - jakby co, to ten Agent to wzorzec projektowy "Mediator" - nazwałem go tak, bo bardziej mi pasuje ale do rzeczy

Kopiuj
Agent::Agent(QObject *parent): AbstractAgent{parent}
{
    this->setObjectName(QString("Agent"));

    QObject::connect(this, &Agent::registerNewWindow, this, &Agent::isRegisteredWindow);
}

void Agent::registerWindow(QWidget *ptrWidget)
{
    bool isRegister = false;

    if(ptrWidget){
        ptrChildWindowSettings = ptrWidget;
        isRegister = true;
        emit registerNewWindow(isRegister);
    } else {
        //obsłuż ten błąd w jakimś oknie -> Errors::RuntimeErrors::NullPointer;
    }
}

void Agent::isRegisteredWindow(bool isRegister)
{
    if(isRegister){
        window = qobject_cast<WindowSettings*>(ptrChildWindowSettings);

        if(window){
            page1 = window->findChild<Page1*>(); //QWidget, który ma kontrolki np QLineEdit
            page2 = window->findChild<Page2*>(); //QWidget, który ma kontrolki np QLineEdit
            
            if(page1){
                QObject::connect(page1, &Page1::ipAddress, this, &Agent::receivedIPAddress);
                //.... itp
            } else {
                //obsłuż ten błąd w jakimś oknie -> Errors::RuntimeErrors::NullPointer;
            }
        } else {
            qInfo()<< "blad uzyskania wskaznika window";
            //obsłuż ten błąd w jakimś oknie -> Errors::RuntimeErrors::NullPointer;
        }
    } else {
        //obsłuż ten błąd w jakimś oknie -> Errors::RuntimeErrors::NullPointer;
    }
}

więc pytanie jest takie - czy w tej funkcji isRegisteredWindow jest sens zagnieżdżać warunki w taki sposób jak pokazałem w kodzie?

Bo załóżmy, że w przyszłości dojdzie jeszcze kilka okien i "agent" będzie musiał jakoś wszystkich ze sobą skomunikować albo obsłużyć ich błędy - no wszystkich to przesada ale na pewno te, które będą musiały ze sobą się wymienić jakimiś informacjami

trochę opisu do kodu
tutaj chodzi o to, że agent musi dostać się do metod publicznych jakichś klas aby uzyskać to, co wpisuje użytkownik i np obsłużyć jakiś błąd, bądź to co wpisze user przekazać to do innego obiektu okna - w tym przypadku zbieram dane od usera i chcę to zrzucić do pliku

MarekR22
  • Rejestracja: dni
  • Ostatnio: dni
3

Żeby mieć mediatora potrzebujesz co najmniej 3 klasy.
Mediatora, który otrzymuje powiadomienia tłumaczy je i rozpropagowuje do innych obiektów różnych klas, które miedzy sobą oczekują niekompatybilnych typów powiadomień.

ZK
  • Rejestracja: dni
  • Ostatnio: dni
0

@MarekR22: no w sumie mam/będę miał więcej niż jedną klasę bo tutaj już jedna klasa WindowSettings jest obsługiwana wraz z obiektami potomnymi typu QWidget jest czyli są to klasy Page1 i Page2 - dalej, z Page1 i z Page2 chcę pobrać dane i zrzucić je do klasy SettingsFile która zajmuje się zapisem ustawień do plików i trzecia klasa to klasa Errors która obsługuje błędy - i te wszystkie rzeczy umieszczam w mediatorze - poza tym i tak dojdzie tam więcej klas np do obsługi dźwięku, video, domeny...

@MarekR22: więc nie licząc obiektów potomnych są już trzy klasy - WindowSettings, Errors i SettingsFile

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10227
0
zkubinski napisał(a):

jak w tytule i oto kawałek kodu - jakby co, to ten Agent to wzorzec projektowy "Mediator" - nazwałem go tak, bo bardziej mi pasuje ale do rzeczy

Ale wiesz że już jest wzorzec który już dawno temu został nazwany "Mediator"? Więc żeby uniknąć nieporozumień, ja bym na Twoim miejscu nazwał ten Twój inaczej 😉

ZK
  • Rejestracja: dni
  • Ostatnio: dni
0
Riddle napisał(a):
zkubinski napisał(a):

jak w tytule i oto kawałek kodu - jakby co, to ten Agent to wzorzec projektowy "Mediator" - nazwałem go tak, bo bardziej mi pasuje ale do rzeczy

Ale wiesz że już jest wzorzec który już dawno temu został nazwany "Mediator"? Więc żeby uniknąć nieporozumień, ja bym na Twoim miejscu nazwał ten Twój inaczej 😉

w sumie racja, bo wtedy cały ten mój opis, który wypociłem chyba byłby zbędny?

SL
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1023
1

Oczywiście wszystko zależy od gustu, ale większość opinii z jakmi się spotkałem mówią, że wcięcia to zło i lepiej postawić na early returns.

Z drugiej strony jest też cała wykładania, że programowanie strukturalne jest ważne, ale IMO to akademickie bajania osób, które nie piszą kodu na co dzień

Chat-GPT tak mi przekształcił:

Kopiuj

if (!isRegister) {
    // Obsłuż ten błąd w jakimś oknie -> Errors::RuntimeErrors::NullPointer;
    return;
}

auto window = qobject_cast<WindowSettings*>(ptrChildWindowSettings);
if (!window) {
    qInfo() << "Błąd uzyskania wskaźnika window";
    // Obsłuż ten błąd w jakimś oknie -> Errors::RuntimeErrors::NullPointer;
    return;
}

auto page1 = window->findChild<Page1*>(); // QWidget, który ma kontrolki np QLineEdit
if (!page1) {
    // Obsłuż ten błąd w jakimś oknie -> Errors::RuntimeErrors::NullPointer;
    return;
}

QObject::connect(page1, &Page1::ipAddress, this, &Agent::receivedIPAddress);
// Dodaj inne akcje...

ZK
  • Rejestracja: dni
  • Ostatnio: dni
0

ok, ale jak patrzę na moje zagnieżdżenia to rozumiem co po kolei idzie, a jak patrzę na early returns to się gubię ale jest przejrzyście... może to kwestia doświadczenia albo co tam się po tym warunku wpisze?

podsumowując, rozumiem early returns zostaje - zaraz wrzucę co zrobiłem

SL
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1023
4
zkubinski napisał(a):

może to kwestia doświadczenia

To. Jak widzę styl z drabinką ifów to mnie zaczyna głowa boleć, więc działa to w obie strony

ZK
  • Rejestracja: dni
  • Ostatnio: dni
0
slsy napisał(a):
zkubinski napisał(a):

może to kwestia doświadczenia

To. Jak widzę styl z drabinką ifów to mnie zaczyna głowa boleć, więc działa to w obie strony

dobra, to co ty na to?

Kopiuj
Mediator::Mediator(QObject *parent): AbstractMediator{parent}
{
    this->setObjectName(QString("Mediator"));

    QObject::connect(this, &Mediator::registerNewWindow, this, [&](bool isRegister){
        Errors::RegisterWindowErrors registerError = this->isRegisteredWindow(isRegister);

        if(registerError != Errors::RegisterWindowErrors::RegisterSuccess){
            qInfo()<< "wywolaj funkcje obslugi bledow";
        }
    });
}

void Mediator::registerWindow(QWidget *ptrWidget)
{
    bool isRegister = false;

    if(ptrWidget){
        ptrChildWindowSettings = ptrWidget;
        isRegister = true;
        emit registerNewWindow(isRegister);
    } else {
        //obsłuż ten błąd w jakimś oknie -> Errors::RuntimeErrors::NullPointer;
    }
}

Errors::RegisterWindowErrors Mediator::isRegisteredWindow(bool isRegister)
{
    if(!isRegister){
        //niezarejestrowane okno ustawień
        //obsłuż ten błąd w jakimś oknie -> Errors::RuntimeErrors::NullPointer;
        return Errors::RegisterWindowErrors::NotRegistered;
    }

    window = qobject_cast<WindowSettings*>(ptrChildWindowSettings);
    if(!window){
        //błąd pobrania adresu
        //obsłuż ten błąd w jakimś oknie -> Errors::RuntimeErrors::NullPointer;
        return Errors::RegisterWindowErrors::NullPointer;
    }

    page1 = window->findChild<Page1*>();
    if(!page1){
        //obsłuż ten błąd w jakimś oknie -> Errors::RuntimeErrors::NullPointer;
        return Errors::RegisterWindowErrors::MissingPage1;
    }

    page2 = window->findChild<Page2*>();
    if(!page2){
        //obsłuż ten błąd w jakimś oknie -> Errors::RuntimeErrors::NullPointer;
        return Errors::RegisterWindowErrors::MissingPage2;
    }

    return Errors::RegisterWindowErrors::RegisterSuccess;
}

klasa Errors

Kopiuj
#ifndef ERRORS_H
#define ERRORS_H

#include <QWidget>
#include <QMessageBox>

class Errors : public QWidget
{
    Q_OBJECT

public:
    explicit Errors(QWidget *parent = nullptr);
    ~Errors();

    //![0-9]
    enum class ApplicationErrors{Success=0, ExecutionError=1, UnkownError}; //błędy specyficzne dla logiki aplikacji i logiką biznesową aplikacji

    //![10-19]
    enum class DatabaseErrors{ConnectionSuccess=10, ConnectionFailed, QueryError, TransactionFailed};

    //![20-29]
    enum class FileErrors{FileIsExists=20, FileNotFound, PermissionDenied, ReadError, WriteError}; //błędy związane z działaniami na plikach

    //![30-39]
    enum class NetworkErrors{SuccessConnection=30, ConnectionTimeout, ConnectionRefused, HostUnreachable}; //błędy związane z komunikacją sieciową i połączeniami sieciowymi

    //![40-49]
    enum class RegisterWindowErrors{RegisterSuccess=40, NotRegistered, NullPointer, MissingPage1, MissingPage2};

    //![50-59]
    enum class RuntimeErrors{OutOfMemory=50, StackOverflow}; //błędy występujące w trakcie działania systemu

    //![60-69]
    enum class SecurityErrors{AuthenticationSuccess=60, AuthenicationFailed, AuthorizationDenied};

    //![70-79]
    enum class SystemErrors{HardwareFailrue=70, SystemCrash, ResourceExhausted}; //błędy związane z działaniem samego systemu

    //![80-89]
    enum class UserErrors{InvalidInput=80, UnauthorizedAction, UserNotFound, ActionNotAllowed}; //błedy spowodowane działaniami użytkownika

    //![90-99]
    enum class ValidationErrors{CorrectFormat=90, InvalidFormat, MissingField, ValueOutOfRange}; //błedy walidacji danych wejściowych

signals:
};

#endif // ERRORS_H

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10227
0
zkubinski napisał(a):

ok, ale jak patrzę na moje zagnieżdżenia to rozumiem co po kolei idzie, a jak patrzę na early returns to się gubię ale jest przejrzyście... może to kwestia doświadczenia albo co tam się po tym warunku wpisze?

Nie chcę za bardzo filozofować, ale to może być symptom tego że myślisz o kodzie implementacją (szczegółami implementacyjnymi), zamiast abstrakcją.

ZK
  • Rejestracja: dni
  • Ostatnio: dni
0

znaczy? bo nie rozumiem co znaczy myśleć implementacją, a myśleć abstrakcją?

RE
  • Rejestracja: dni
  • Ostatnio: dni
1

https://helion.pl/ksiazki/refaktoryzacja-ulepszanie-struktury-istniejacego-kodu-wydanie-ii-martin-fowler,refak2.htm#format/d

wzroce projektowe się stosuje do rozwiązania problemów a nie ich wywołania kiedy jeszcze problem się nie pojawił.

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10227
1
zkubinski napisał(a):

znaczy? bo nie rozumiem co znaczy myśleć implementacją, a myśleć abstrakcją?

No mam na myśli to że jak piszemy kod, to ogólnie są dwie istotne rzeczy na temat wywołania metod. Co próbujesz zrobić (również nazywane domeną, biznesem, abstrakcją, kontraktem), oraz jak to próbujesz zrobić (również nazywane implementacją, rozwiązaniem, sposobem, etc.).

Weźmy np. Twój kod z Twojego pierwszego postu:

zkubinski napisał(a):

trochę opisu do kodu
tutaj chodzi o to, że agent musi dostać się do metod publicznych jakichś klas aby uzyskać to, co wpisuje użytkownik i np obsłużyć jakiś błąd, bądź to co wpisze user przekazać to do innego obiektu okna - w tym przypadku zbieram dane od usera i chcę to zrzucić do pliku

Jak to czytam, to ja sobie interpretuję Twój post tak:

tutaj chodzi o to, że `agent` musi dostać się do metod publicznych jakichś klas - implementacja (jak)
aby uzyskać to, co wpisuje użytkownik - problem (co)
i np obsłużyć jakiś błąd - problem (co)
bądź to co wpisze user przekazać to do innego obiektu okna - implementacja (jak)
w tym przypadku zbieram dane od usera i chcę to zrzucić do pliku - tutaj nie jestem do końca pewien - ale to chyba też jest wymaganie (co)

Moim zdaniem najważniejsze jest aby uzyskać to, co wpisuje użytkownik, i np obsłużyć jakiś błąd i i chcę to zrzucić do pliku, czyli to co program ma robić.

Więc odpowiadając na Twoje pytanie, moim zdaniem im szybciej odejdziesz od takiego czytania "zagnieżdżeń ifów na kupę", tym lepiej dla Ciebie, bo wejdziesz na wyższy poziom abstrakcji.

AN
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 989
1

@Riddle problem z Twoim podejściem jest taki, że ostatecznie i tak trzeba zejść do najniższej warstwy abstrakcji i to zaimplementować

ZK
  • Rejestracja: dni
  • Ostatnio: dni
0
Riddle napisał(a):
zkubinski napisał(a):

znaczy? bo nie rozumiem co znaczy myśleć implementacją, a myśleć abstrakcją?

No mam na myśli to że jak piszemy kod, to ogólnie są dwie istotne rzeczy na temat wywołania metod. Co próbujesz zrobić (również nazywane domeną, biznesem, abstrakcją, kontraktem), oraz jak to próbujesz zrobić (również nazywane implementacją, rozwiązaniem, sposobem, etc.).

no to teraz trzeba się dowiedzieć co znaczy domena w języku programowania, biznes to wiem - rozumiem to jako okno, abstrakcji to chyba "mechanika", a kontakt?

Weźmy np. Twój kod z Twojego pierwszego postu:

zkubinski napisał(a):

trochę opisu do kodu
tutaj chodzi o to, że agent musi dostać się do metod publicznych jakichś klas aby uzyskać to, co wpisuje użytkownik i np obsłużyć jakiś błąd, bądź to co wpisze user przekazać to do innego obiektu okna - w tym przypadku zbieram dane od usera i chcę to zrzucić do pliku

Jak to czytam, to ja sobie interpretuję Twój post tak:

tutaj chodzi o to, że `agent` musi dostać się do metod publicznych jakichś klas - implementacja (jak)
aby uzyskać to, co wpisuje użytkownik - problem (co)
i np obsłużyć jakiś błąd - problem (co)
bądź to co wpisze user przekazać to do innego obiektu okna - implementacja (jak)
w tym przypadku zbieram dane od usera i chcę to zrzucić do pliku - tutaj nie jestem do końca pewien - ale to chyba też jest wymaganie (co)

to jest przydatne to co napisałeś, bo dałeś mi rozwiązanie na podejście do problemu - mimo, że to jeden przykład, to jest to warte uwagi

Moim zdaniem najważniejsze jest aby uzyskać to, co wpisuje użytkownik, i np obsłużyć jakiś błąd i i chcę to zrzucić do pliku, czyli to co program ma robić.

Więc odpowiadając na Twoje pytanie, moim zdaniem im szybciej odejdziesz od takiego czytania "zagnieżdżeń ifów na kupę", tym lepiej dla Ciebie, bo wejdziesz na wyższy poziom abstrakcji.

ok, rozumiem

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10227
0
anonimowy napisał(a):

@Riddle problem z Twoim podejściem jest taki, że ostatecznie i tak trzeba zejść do najniższej warstwy abstrakcji i to zaimplementować

Naturalnie, jeśli zachodzi potrzeba żeby zmienić implementację - tak.

Ale często nie potrzeba zmieniać implementacji, czasem chcemy po prostu dowiedzieć się co robi kod - i wtedy pomaga przeczytać wysokopoziomowy opis tego co aplikacja ma robić.

Dla mnie takie podejście działa, i wiem że wiele udanych projektów działa w taki sposób. Dodatkowo - weź pod uwagę, jak duże aplikacje jesteśmy w stanie napisać myśląc tylko implementacją? Czy przyjemnie się czyta klasę która składa się np. z 2000 linijek samej implementacji? Jak w niej wtedy zmienić jedno z kluczowych założeń? Jest to zdecydowanie trudniejsze, niż gdybyśmy składali program z wyżej poziomowych zadań, które stopniowo schodzą w dół.

Ja nie wyobrażam sobie pracować z projektem który nie ma odpowiednich warstw abstrakcji - może jeszcze do tego nie dorosłem 😅

ZK
  • Rejestracja: dni
  • Ostatnio: dni
0
Riddle napisał(a):
anonimowy napisał(a):

@Riddle problem z Twoim podejściem jest taki, że ostatecznie i tak trzeba zejść do najniższej warstwy abstrakcji i to zaimplementować

Naturalnie, jeśli zachodzi potrzeba żeby zmienić implementację - tak.

Ale często nie potrzeba zmieniać implementacji, czasem chcemy po prostu dowiedzieć się co robi kod - i wtedy pomaga przeczytać wysokopoziomowy opis tego co aplikacja ma robić.

Dla mnie takie podejście działa, i wiem że wiele udanych projektów działa w taki sposób. Dodatkowo - weź pod uwagę, jak duże aplikacje jesteśmy w stanie napisać myśląc tylko implementacją? Czy przyjemnie się czyta klasę która składa się np. z 2000 linijek samej implementacji? Jak w niej wtedy zmienić jedno z kluczowych założeń? Jest to zdecydowanie trudniejsze, niż gdybyśmy składali program z wyżej poziomowych zadań, które stopniowo schodzą w dół.

Ja nie wyobrażam sobie pracować z projektem który nie ma odpowiednich warstw abstrakcji - może jeszcze do tego nie dorosłem 😅

czyli jednak UML się przydaje? Bo chyba do tego zmierzasz?

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10227
0
zkubinski napisał(a):

czyli jednak UML się przydaje? Bo chyba do tego zmierzasz?

Nieee 😄 Nie o to mi chodziło.

Miałem na myśli to, żeby zachęcić do tego co proponowali poprzednicy; żeby odejść od zagnieżdżonych ifów, i zacząć wydzielać pomocnicze metody - tylko zwróciłbym uwagę, żeby je odpowiednio nazywać. Warto się kierować tym co aplikacja ma robić, wydzielając metody; i zostawić jak w niższych warstwach.

MY
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1107
1
zkubinski napisał(a):

ok, ale jak patrzę na moje zagnieżdżenia to rozumiem co po kolei idzie, a jak patrzę na early returns to się gubię ale jest przejrzyście... może to kwestia doświadczenia albo co tam się po tym warunku wpisze?

W pracy odziedziczyłem takie cudo z wielopoziomowymi if'ami gdzie całość nie mieściła się na jednym ekranie, a miała ok 300 linijek i 5 poziomów. Na szybko nie wiadomo było co dana metoda robi. Co więcej po każdym if'ie był else wypisujący komunikat kilka ekranów dalej. Analiza tego była masakryczna. Po przejściu na eraly return dało się lepiej przeanalizować, zrefaktorować i naprawić błąd w metodzie.

Dla Ciebie jest to jasne, bo sam to pisałeś. Wcale nie jest powiedziane, że tak by było jakbyś miał do czynienia z taką sytuacją jak Ci opisałem.

Co prawda są ludzie którzy twierdzą, że funkcja powinna mieć jeden return i używanie early returna jest sprzeczne właśnie z tą zasadą. Jednak jestem zdania, że tutaj można zrobić odstępstwo od tej reguły, bo trzymanie się jej i tworzenie wielopoziomowych if'ów jest gorsze niż kilka wyjść z funkcji.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.