Wątek przeniesiony 2023-03-02 11:00 z Off-Topic przez Riddle.

Instrukcja goto

0
Riddle napisał(a):

A co jest nie tak z wydzieleniem kodu do funkcji tak jak napisałem, i potem tych dwóch funkcji do klasy? Wtedy nie mamy problemu z niepotrzebnie widocznymi funkcjami.

Nieważne jak się wspólne fragmenty wydzieli, wymusi to naklepanie większej ilości kodu oraz wymusi duplikację (wywołań tych funkcji). Plus wydzielenie fragmentów do funkcji innych niż osadzone (czyli np. do prywatnych metod klasy) spowoduje spuchnięcie klasy okna i uwidoczni logikę, która nie powinna być widoczna. Najgorszym rozwiązaniem jest wydzielenie tych fragmentów do osobnej klasy, bo wymusi to naklepanie jeszcze większej ilości kodu, uwidoczni całą logikę tych fragmentów (bagatela jednorazowych) i duplikację wywołań, a także wymusi przekazywanie danych w parametrach (lub operowanie na globalnej referencji formularza).

To co podałem wyżej to był krótki i prosty przykład, acz obsługujący wiele rozgałęzień. Teraz spróbuj wykluczyć goto w poniższym zdarzeniu — tym razem zdarzenie kontrolujące rozkaz zamknięcia projektu. Dwie flagi (Modified oraz Stored), cztery różne dialogi potwierdzające (cztery różne sytuacje, bo cztery kombinacje stanów tych dwóch flag) plus możliwość powtórzenia wyboru pliku docelowego.

Logika siedzi w jednym zdarzeniu, brak duplikacji, brak obfuskacji pętlami, brak uwidoczniania jednorazowych fragmentów, obsługa absolutnie wszystkich przypadków (i co istotne, w odpowiedniej kolejności), kod krótki, zwięzły, z jawnym przepływem sterowania, łatwy do zrozumienia i wybitnie prosty do debuggowania.

procedure TFormMain.MenuItemFileCloseClick(ASender: TObject);
label
  ProjectClose, DialogProjectOpen, DialogProjectPrepare;
begin
  if not FontsEditor.Project.Modified then
    if FontsEditor.Dialogs.Message.Execute('Chcesz zamknąć projekt?', MB_ICONQUESTION or MB_YESNO or MB_DEFBUTTON2) = mrYes then
      goto ProjectClose
    else
      exit;

  if FontsEditor.Project.Stored then
  case FontsEditor.Dialogs.Message.Execute('Chcesz zapisać zmiany?', MB_ICONWARNING or MB_YESNOCANCEL or MB_DEFBUTTON3) of
    mrCancel: exit;
    mrNo:     goto ProjectClose;
    mrYes:
      if FontsEditor.Project.SaveToFile(FontsEditor.Project.FileName, True) then
        goto ProjectClose
      else
      begin
        FontsEditor.Dialogs.Message.Execute('Błąd zapisu, nie zamykam projektu.', MB_ICONERROR or MB_OK or MB_DEFBUTTON1);
        exit;
      end;
  end
  else
  case FontsEditor.Dialogs.Message.Execute('Chcesz wybrać plik i zapisać projekt?', MB_ICONWARNING or MB_YESNOCANCEL or MB_DEFBUTTON3) of
    mrCancel: exit;
    mrNo:     goto ProjectClose;
    mrYes:    goto DialogProjectPrepare;
  end;

DialogProjectPrepare:
  DialogSaveProject.FileName   := 'new font.bin';
  DialogSaveProject.InitialDir := GetCurrentDir();

DialogProjectOpen:
  if DialogSaveProject.Execute() then
    if FontsEditor.Project.SaveToFile(DialogSaveProject.FileName, True) then
      goto ProjectClose
    else
      if FontsEditor.Dialogs.Message.Execute('Błąd zapisu, chcesz wybrać inny plik?', MB_ICONERROR or MB_YESNO or MB_DEFBUTTON2) = mrYes then
        goto DialogProjectOpen;

  exit;

ProjectClose:
  FontsEditor.Project.Close();
  // Aktualizacja kontrolek interfejsu.
end;

Treść messageboxów uprościłem, żeby łatwiej było zrozumieć o co program pyta użytkownika.

1

Nie udało się, bo zmienił się przepływ sterowania i dialog wyświetla się zawsze, a nie tylko wtedy, gdy zmiany zostały wprowadzone i gdy użytkownik zamknął okno (w celu anulowania zmian i ewakuacji z okna ustawień).

No nie za bardzo sie zgadzam:

  1. W twoim kodzie wartość FSaving jest zmieniana na false tylko jeżeli ma wartość true
  2. Dialog pokaże się tylko gdy FSaving = false i FModified=true

Jak zmienie warunek w tym odwróconym ifie:

Procedure TFormSettings.FormCloseQuery(ASender: TObject; var ACanClose: Boolean);
var
  PathRoot:   String;
  PathEditor: String;
begin
	if FModified and not FSaving  then
	case FontsEditor.Dialogs.Message.Execute({...}, {...}, MB_ICONWARNING or MB_YESNOCANCEL or MB_DEFBUTTON3) of
	  mrCancel: ACanClose := False;
	  mrNo:     ACanClose := True;
	  mrYes:    FSaving := True;
	end;

	if FSaving then
	begin
		FSaving := False;

		PathRoot   := EditPathRoot.Text;
		PathEditor := EditPathEditor.Text;

		if not ValidatePathRoot(PathRoot)     then begin ACanClose := False; exit; end;
		if not ValidatePathEditor(PathEditor) then begin ACanClose := False; exit; end;

		EditPathRoot.Text   := PathRoot;
		EditPathEditor.Text := PathEditor;

		ModalResult := mrOK;
	end
end;

To tak samo dialog pokażesz tylko dla prawdziwego FModified i falszywego FSaving,a nie "zawsze"

Zmiana flagi FSaving w 1 ifie nie ma znaczenia bo i tak wracam do pierwotnego stanu w pierwszej instrukcji 2 if-a. Wskaż mi proszę w którym miejscu zmieniłem przepływ.

1

Masz rację, da się wykorzystać flagę FSaving jako zmiennej lokalnej, Twój przykład również jest prawidłowy. Za bardzo martwiłem się o to, aby flaga FSaving zawsze była gaszona w tym zdarzeniu i nie pomyślałem o tym, aby jej użyć w taki sposób. Brawo.

Ktoś chętny na refaktor dugiego przykładu? ;)

0

Ktoś chętny na refaktor dugiego przykładu? ;)

To nie jest miły przykład zwłaszcza jakie warunki dajesz, które do końca mnie nie przekonują:

Nieważne jak się wspólne fragmenty wydzieli, wymusi to naklepanie większej ilości kodu

Ok, mam zdrowe palce więcej kodu to nie jest źle, nie wiem skąd u Ciebie takie parcie na ograniczanie tych linijek.

oraz wymusi duplikację (wywołań tych funkcji).

Wytłumacz mi dlaczego wywołanie tych funkcji nawet kilkukrotne, jest gorsze od użycia 5x goto ProjectClose

Plus wydzielenie fragmentów do funkcji innych niż osadzone (czyli np. do prywatnych metod klasy) spowoduje spuchnięcie klasy okna

ja nie jestem free pascalowcem nie wiem co to jest osadzona funkcja, ale nie przekonasz mnie, że logiczny podział kodu jest gorszy niż goto. Puchnięcie klasy to ok, ale nie popadajmy w skrajności

i uwidoczni logikę, która nie powinna być widoczna.

Widoczna gdzie? Chyba jeżeli będzie jako prywatna to spoza formularza nie będzie.

Najgorszym rozwiązaniem jest wydzielenie tych fragmentów do osobnej klasy, bo wymusi to naklepanie jeszcze większej ilości kodu, uwidoczni całą logikę tych fragmentów (bagatela jednorazowych) i duplikację wywołań,

Jeżeli funkcjonalność którą kodujesz będzie wymagała stworzenia klasy to tak wypada zrobić

a także wymusi przekazywanie danych w parametrach (lub operowanie na globalnej referencji formularza).

No chyba na tym polega programowanie.

Popatrz na swój snippet z pozycji osoby która wgryza sie w system, co byś nie powiedział, czytając kod z dołu do góry, trafiłby mnie szlag jakbym zobaczył to exit przed ProjectClose: Dla mnie to świadczy o tym, ze programista nie potrafi napisać warunków, żeby wykonać główne zadanie funkcji.

Poza tym nie widzę w tym kodzie czytelności np. kod zapisujący projek masz w 2 miejscach, czyli prosta funkcjonalność: Jeżeli były zmiany to zapisz a później zamknij wymaga w Twoim kodzie 7 instrukcji goto i 5x exit

Ja bym wolał zobaczyć coś takiego:

procedure TFormMain.MenuItemFileCloseClick(ASender: TObject);
begin
	if FontsEditor.Project.Modified  then
		begin
			if SaveProject() then
				ProjectClose()
		end;
	else
		if FontsEditor.Dialogs.Message.Execute('Chcesz zamknąć projekt?', MB_ICONQUESTION or MB_YESNO or MB_DEFBUTTON2) = mrYes then
			ProjectClose()
End

A reszta w ten deseń:

function SaveProject() as Boolean;
begin
	if FontsEditor.Project.Stored then
		DialogMessage := 'Chcesz zapisać zmiany?'
	else
		DialogMessage := 'Chcesz wybrać plik i zapisać projekt?'
	end
 
	case FontsEditor.Dialogs.Message.Execute(DialogMessage, MB_ICONWARNING or MB_YESNOCANCEL or MB_DEFBUTTON3) of
		mrCancel: SaveProject := False;
		mrNo:     SaveProject := True;
		mrYes:    
			begin
				if FontsEditor.Project.Stored then
					SaveProject = SaveStoredProject()
				else
					SaveProject = SevaeNonStoredProject()
			end;
		end;
end;
function SaveStoredProject() as Boolean;
begin
     if FontsEditor.Project.SaveToFile(FontsEditor.Project.FileName, True) then
        SaveStoredProject = True 
      else
      begin
        FontsEditor.Dialogs.Message.Execute('Błąd zapisu, nie zamykam projektu.', MB_ICONERROR or MB_OK or MB_DEFBUTTON1);
        SaveStoredProject = False
      end;	
end
function SaveNonStoredProject() as Boolean;
begin
//Nie chce mi się zmieniać tego na pętle
DialogSaveProject.FileName   := 'new font.bin';
DialogSaveProject.InitialDir := GetCurrentDir();
SaveNonStoredProject := False
DialogProjectOpen:
  if DialogSaveProject.Execute() then
    if FontsEditor.Project.SaveToFile(DialogSaveProject.FileName, True) then
      SaveNonStoredProject := True
    else
      if FontsEditor.Dialogs.Message.Execute('Błąd zapisu, chcesz wybrać inny plik?', MB_ICONERROR or MB_YESNO or MB_DEFBUTTON2) = mrYes then
        goto DialogProjectOpen;

end;
function ProjectClose()
begin
	FontsEditor.Project.Close();
	// Aktualizacja kontrolek interfejsu.
end;

Czyli z tej złej duplikacji mam 2x wywołanie ProjectClose zamiast 5x goto w jednym miejscu zapis projektu zamiast 2, żadnego exit => czytelny kod bez udziwnień

P.S. Pascala ostatnio używałem ponad 20 lat temu więc nie czepiajmy się składni

0

TLDR;-) 10 stron o goto, nie wiedziałem, że to tak fascynujący temat. Zaprzepaściłem wszystko, bo ostatni raz użyłem goto, pisząc jakąś grę strzelankę "UFO" w Turbo Pascalu w latach 90tych;-)

0
furious programming napisał(a):

Nieważne jak się wspólne fragmenty wydzieli, wymusi to naklepanie większej ilości kodu oraz wymusi duplikację (wywołań tych funkcji).

Przecież goto to tylko upośledzone wywołanie funkcji bez powrotu, czym się różni napisanie 5x "goto ProjectClose" od napisania "ProjectClose()". W najprostszej wersji można to zrefaktorować do:

procedure TFormMain.MenuItemFileCloseClick(ASender: TObject);
begin
  if not FontsEditor.Project.Modified then begin
    if FontsEditor.Dialogs.Message.Execute('Chcesz zamknąć projekt?', MB_ICONQUESTION or MB_YESNO or MB_DEFBUTTON2) = mrYes then
      ProjectClose;
    exit;
  end

  if FontsEditor.Project.Stored then
    case FontsEditor.Dialogs.Message.Execute('Chcesz zapisać zmiany?', MB_ICONWARNING or MB_YESNOCANCEL or MB_DEFBUTTON3) of
      mrNo:     ProjectClose;
      mrYes:
        if FontsEditor.Project.SaveToFile(FontsEditor.Project.FileName, True) then
          ProjectClose
        else
          FontsEditor.Dialogs.Message.Execute('Błąd zapisu, nie zamykam projektu.', MB_ICONERROR or MB_OK or MB_DEFBUTTON1);
    end
  else
    case FontsEditor.Dialogs.Message.Execute('Chcesz wybrać plik i zapisać projekt?', MB_ICONWARNING or MB_YESNOCANCEL or MB_DEFBUTTON3) of
      mrNo:     ProjectClose;
      mrYes:    DialogProjectOpen;
    end
end

procedure DialogProjectOpen()
begin
  DialogSaveProject.FileName   := 'new font.bin';
  DialogSaveProject.InitialDir := GetCurrentDir();

  repeat
    if DialogSaveProject.Execute() then
      if FontsEditor.Project.SaveToFile(DialogSaveProject.FileName, True) then
        ProjectClose
      else
        if FontsEditor.Dialogs.Message.Execute('Błąd zapisu, chcesz wybrać inny plik?', MB_ICONERROR or MB_YESNO or MB_DEFBUTTON2) = mrYes then
          continue
    exit
  until false
end

procedure ProjectClose()
begin
  FontsEditor.Project.Close();
  // Aktualizacja kontrolek interfejsu.
end;

ależ napuchło, 45 linijek zamiast 48. Za to brak wszechobecnego powtarzania "exit"

0
Panczo napisał(a):

To nie jest miły przykład zwłaszcza jakie warunki dajesz, które do końca mnie nie przekonują:

Nie daję żadnych warunków — ot podałem przykładowe zdarzenie i jeśli ktoś chce spróbować zwiększyć czytelność poprzez zastąpienie goto czymś innym, to ma taką możliwość.

Ok, mam zdrowe palce więcej kodu to nie jest źle, nie wiem skąd u Ciebie takie parcie na ograniczanie tych linijek.

Bo im mniej kodu w projekcie, tym mniej energii trzeba poświęcić na — jak to ująłeś — wgryzienie się w projekt.

Wytłumacz mi dlaczego wywołanie tych funkcji nawet kilkukrotne, jest gorsze od użycia 5x goto ProjectClose

Już o tym pisałem, ze dwa razy. Stworzyłeś procedury z wydzielonymi fragmentami logiki, które to fragmenty nie powinny być widoczne poza pierwotne zdarzenie. Twoje procedury nie dość, że będą widoczne dla wszystkich metod znajdujących się w module pod nimi (czyli będzie można je wywołać), to w dodatku zawierają szczątkową logikę — ich wywołania z innych miejsc spowodują nieprawidłowe działanie programu (inicjalizacja dialogów jest w zdarzeniu, a ich otwarcie w procedurze).

ja nie jestem free pascalowcem nie wiem co to jest osadzona funkcja […]

To funkcja zadeklarowana w całości wewnątrz innej — nazywa się to zagnieżdżaniem, nie osadzaniem (źle podałem wcześniej).

procedure Main();
  procedure Foo(); // procedura osadzona w Main (pierwszy poziom zagnieżdżenia)
    procedure Baz(); // procedura osadzona w Foo (drugi poziom zagnieżdżenia)
    begin
      // baz body
    end;
  begin
    // Foo body
  end;
begin
  // main body
end;

W ten sposób można wydzielać wspólne fragmenty logiki, jednocześnie dając możliwość wykorzystania osadzonych funkcji tylko w obrębie funkcji głównej, w której są zadeklarowane. Czyli w skrócie, w ten sposób można ograniczać zasięg.

[…] ale nie przekonasz mnie, że logiczny podział kodu jest gorszy niż goto. Puchnięcie klasy to ok, ale nie popadajmy w skrajności

Logiczny podział nie byłby gorszy, ale w przypadku tego zdarzenia logicznym by nie był. Pewnie, można by stworzyć jakiś writer projektu i jemu nakazać obsługę zapisu. Ale co z dialogami? Writer miałby być odpowiedzialny za zapis danych, nie za interfejs i interakcję z użytkownikiem. Nic by to nie zmieniło, nadal trzeba by dialogi ogarniać w zdarzeniu, aby odpowiedzialność podsystemów była odpowiednia (ograniczona do minimum).

Widoczna gdzie? Chyba jeżeli będzie jako prywatna to spoza formularza nie będzie.

Wszędzie. W przypadku lokalnych procedur dla modułu (sekcja implementation, tak jak domniemam, że zrobiłeś w przykładzie), będą widoczne dla wszystkich podprogramów zaimplementowanych poniżej ich implementacji (innych lokalnych podprogramów, metod formularza, zawartości ewentualnych plików dołączanych). Jeśli je wydzielić do prywatnych metod klasy okna, to jeszcze gorzej, bo będą widoczne w całym module, a nie tylko w jego części.

Jeżeli funkcjonalność którą kodujesz będzie wymagała stworzenia klasy to tak wypada zrobić

Zgadzam się, jednak ten przypadek nie wymaga stworzenia osobnej klasy. Co najwyżej można podzielić go na mniejsze metody/funkcje jednorazowe, jeśli dla kogoś tak będzie wygodniej.

Popatrz na swój snippet z pozycji osoby która wgryza sie w system, co byś nie powiedział, czytając kod z dołu do góry […]

Dlaczego małby ktoś musieć czytać kod z dołu do góry, skoro kolejność instrukcji wymaga czytania z góry do dołu? W pierwszej kolejności wykonywane są warunki, potem skok na dół lub exit, jeśli całe zdarzenie musi być przerwane (bo uzytkownik się rozmyślił i wcisnął Cancel w którymś oknie dialogowym). Tu nie ma żadnej magii, skakania w górę i w dół, a tym bardziej czytania kodu z dołu do góry. Wszystko wykonywane jest sekwencyjnie, a tam gdzie są exity, tam muszą być.

Jeśli podzielisz kod na jednorazowe funkcje, to nadal warunki będą wykonywane najpierw, ale zamiast robić skok w dół, wywoła się po prostu tę funkcję. W zależności jak się drabinki zapisze, będzie można pominąć exity lub nie. Nadal użytkownik może się rozmyślić, więc cały proces musi być przerwany, a więc rezultaty tych poszczególnych jednorazowych funkcji muszą być sprawdzane.

[…] trafiłby mnie szlag jakbym zobaczył to exit przed ProjectClose:

exit musi tam być, na wypadek, gdyby użytkownik anulował dialog. W przeciwnym razie anulowanie będzie działać tak samo jak akceptacja, czyli tragedia.

Dla mnie to świadczy o tym, ze programista nie potrafi napisać warunków, żeby wykonać główne zadanie funkcji.

Ależ potrafi — i je napisał. ;)

Z punktu widzenia realizacji założeń co do tego zdarzenia, forma z goto działa prawidłowo dla każdego przypadku. Dodatkowo, nie udostępnia na zewnątrz zdarzenia żadnych nieuniwersalnych fragmentów i może być w trywialny sposób reprezentowana przez prostą listę kroków.


obscurity napisał(a):

Przecież goto to tylko upośledzone wywołanie funkcji bez powrotu, czym się różni napisanie 5x "goto ProjectClose" od napisania "ProjectClose()".

Już o tym pisałem.

W najprostszej wersji można to zapisać jako:

Ta alternatywa nie działa tak samo jak pierwowzór. W dodatku zamieniłeś goto na pętlę, czym niepotrzebnie sugerujesz, że tam coś ma działać w pętli, choć nie ma. To raczej kwestia gustu, ale dla mnie etykieta jasno pokazuje, że skok ma nastąpić w specyficznym przypadku. Pętli używam tylko tam, gdzie kod ma być typowo zapętlony, bo zwykle wykonuje fragment wielokrotnie.

0

Przecież goto to tylko upośledzone wywołanie funkcji bez powrotu, ...

Ale nie zawsze potrzebny jest ten powrót.

0

To jest tylko bzdurne zdarzenie do komunikacji z użytkownikiem, więc jeśli ktoś woli podział na mniejsze funkcje, to nie ma problemu. Natomiast w przypadku kodu, który musi być wydajny tak bardzo jak tylko się da, podział na małe funkcje i ich wywoływanie negatywnie odbije się na wydajności. Zostanie co najwyżej inlineowanie i liczenie na to, że kompilator te wywołania zoptymalizuje.

0
Manna5 napisał(a):

Przecież goto to tylko upośledzone wywołanie funkcji bez powrotu, ...

Ale nie zawsze potrzebny jest ten powrót.

No to w tym przypadku wystarczy dopisać return po wywołaniu i mieć jasne wyjście z funkcji.
Zauważ że w przykładzie furious musiał ciągle wywoływać nadmiarowo "exit" bo musi przeskoczyć elementy kodu których nie chce wywoływać. Gorzej jak w którymś miejscu zapomnimy zrobić przeskoku lub go zgubimy przy refaktoringu i wywoła się zupełnie przypadkowy kod. Po to wymyślono funkcje / procedury / metody żeby nie bawić się w ręczne skakanie, zostawmy skoki asemblerowi.

0
obscurity napisał(a):

Zauważ że w przykładzie furious musiał ciągle wywoływać nadmiarowo "exit" bo musi przeskoczyć elementy kodu których nie chce wywoływać.

Nie, exit wywoływany jest tylko wtedy, gdy użytkownik anuluje któregokolwiek messageboxa i cały proces musi zostać przerwany. Zauważ, że cała logika jest w jednym zdarzeniu, więc każdy exit przerywa całe zdarzenie. Przy podziale na mniejsze funkcje, jak to zrobiłeś, exit nie jest potrzebny, ale potrzebne jest sprawdzanie rezultatu tych małych funkcji.

0
furious programming napisał(a):

To jest tylko bzdurne zdarzenie do komunikacji z użytkownikiem, więc jeśli ktoś woli podział na mniejsze funkcje, to nie ma problemu. Natomiast w przypadku kodu, który musi być wydajny tak bardzo jak tylko się da, podział na małe funkcje i ich wywoływanie negatywnie odbije się na wydajności. Zostanie co najwyżej inlineowanie i liczenie na to, że kompilator te wywołania zoptymalizuje.

To jest bzdurny argument który może miał sens w latach 80 / 90. Pomijając że kompilator prawie na pewno to zinlinuje to mówimy o różnicach rzędu nanosekund. Śmieszna jest próba oszczędzenia dwóch nanosekund kosztem czytelności gdy pokazujemy użytkownikowi dialog z plikami który otwiera się przez ponad sekundę - miliardy razy dłużej. Nawet w krytycznych partiach kodu tego typu optymalizacje rzadko kiedy są sensowne.

0
obscurity napisał(a):

To jest bzdurny argument który może miał sens w latach 80 / 90. Pomijając że kompilator prawie na pewno to zinlinuje to mówimy o różnicach rzędu nanosekund.

Prawie na pewno to za mało. Poza tym, różnica nanosekund (raczej mikrosekund, jeśli mowa o operacji na stosie/stercie kosztują) w przypadku kodu wykonującego miliony i więcej małych czynności, tracisz te nanosekundy pomnożone przez liczbę czynności (a więc miliony).

Śmieszna jest próba oszczędzenia dwóch nanosekund kosztem czytelności gdy pokazujemy użytkownikowi dialog z plikami który otwiera się przez ponad sekundę - miliardy razy dłużej.

Kto powiedział, że w tym edytorze użyłem goto dla optymalizacji wydajności? Bo ja na pewno nie.

Nawet w krytycznych partiach kodu tego typu optymalizacje rzadko kiedy są sensowne.

No chyba jednak nie. Taki SDL posiada masę goto, właśnie po to aby maksymalnie zwiększyć wydajność i nie mnożyć niepotrzebnych, w tym jednorazowych funkcji.

0
furious programming napisał(a):

No chyba jednak nie. Taki SDL posiada masę goto, właśnie po to aby maksymalnie zwiększyć wydajność.

Dlatego napisałem "rzadko kiedy" a nie "nigdy". Faktycznie są przypadki gdy da się coś uczknąć w ten sposób, widziałem też tricki gdzie kopiowano kilka razy tę samą linijkę w pętli bo było to szybsze niż zrobienie kilka razy więcej iteracji pętli.
Ale tym można się przejmować pisząc sterownik, bibliotekę niskiego poziomu, system; na pewno nie pisząc zwykły backend czy aplikację desktopową. Kompilator powinien się zająć optymalizacją takich rzeczy.

0
obscurity napisał(a):

Dlatego napisałem "rzadko kiedy" a nie "nigdy".

Tyle że to „rzadko kiedy” należy rozwinąć do „często” — jeśli mowa o systemach, w których kładzie się nacisk na wydajność oraz które tworzone są np. w C (bo wspiera goto). Nadźganie ifów to zły pomysł, bo branch predictor będzie zamulać.

Faktycznie są przypadki gdy da się coś uczknąć w ten sposób, widziałem też tricki gdzie kopiowano kilka razy tę samą linijkę w pętli bo było to szybsze niż zrobienie kilka razy więcej iteracji pętli.

Owszem, i wtedy regułę DRY wsadza się w buty.

0

Tylko sprostuje, chciałem napisać że czytamy z góry do dołu.
@furious programming: ja się gubię. Dajesz snippet, w którym twierdzisz że tylko goto jest najlepszym rozwiązaniem i jest to uzasadnione bo to FP i nie ma defer itd.

W odpowiedzi dostajesz czytelny kod, to argumentujesz o puchnięciu kodu, bezsensowności takiego rozwiązania.I na końcu dochodzimy do optymalizacji, i że to wszystko pod wydajność.

Mam wrażenie, że co byś nie dostał w odpowiedzi to znajdziesz powód, że goto jest lepsze. Prezentujesz tak ugruntowaną postawę wobec goto, że dyskusja z Tobą z mojego punktu widzenianie ma sensu, do niczego się nie przekonamy.

0
Panczo napisał(a):

W odpowiedzi dostajesz czytelny kod, to argumentujesz o puchnięciu kodu, bezsensowności takiego rozwiązania.

Nie twierdziłem, że propozycje są bezsensowne, a że mają swoje wady — a to ogromna różnica.

I na końcu dochodzimy do optymalizacji, i że to wszystko pod wydajność.

Tylko dlatego, że @obscurity rozwinął temat ogólnej sensowności używania goto, a nie dlatego, że ma to cokolwiek wspólnego z moim snippetem. No bo przecież nie ma i nigdzie nie twierdziłem, że w swoim snippecie właśnie z tego powodu go używam.

Mam wrażenie, że co byś nie dostał w odpowiedzi to znajdziesz powód, że goto jest lepsze.

Ja natomiast mam wrażenie, że za każdym razem gdy o coś pytam lub coś prezentuję i pojawiają się alternatywne spojrzenia, to odpowiadający uważają, że mam obowiązek się z ich wypowiedziami zgadzać bezkrytycznie, a jeśli się nie zgodzę, to nazywany jestem bucem lub ekstremistą (tutaj jeszcze nie — jeszcze).

Podaliście przykładowe rozwiązania wykluczające goto (prawie, bo w Twoim przykładzie zostało jedno, do ponownego otwarcia dialogu). Napisałem, że nie podoba mi się taki podział i podałem co dokładnie — mnożenie jednorazowych funkcji (plus opcjonalnie puchnięcie klasy), zbędne uwidocznianie nieuniwersalnej logiki, trudniejsze debugowanie (48 linijek spójnej logiki rozbito na pięć funkcji na łącznie 65 linijek) itd.

Nie uważam, że wasze propozycje są złe, a że po prostu w moim odczuciu nie są wyraźnie lepsze.

3
furious programming napisał(a):
obscurity napisał(a):

Dlatego napisałem "rzadko kiedy" a nie "nigdy".

Tyle że to „rzadko kiedy” należy rozwinąć do „często” — jeśli mowa o systemach, w których kładzie się nacisk na wydajność oraz które tworzone są np. w C (bo wspiera goto). Nadźganie ifów to zły pomysł, bo branch predictor będzie zamulać.

W językach zorientowanych na wydajność - C, C++, Zig, Rust, najczęściej są dostępne mechanizmy kontroli inline'owania przez programistę.
Mimo że kompilator niemal na pewno połączy bardzo małe funkcje i zrobi to na 99% lepiej niż programista, programista ma nadal pozostawioną możliwość sterowania tym procesem ręcznie. Przykładowo w Rust mamy #[inline] #[inine(always)] oraz #[inline(never)] i one naprawdę wpływają na generowany kod. Oprócz tego obecnie optymalizacje inline potrafią robić też linkery, więc inline działa nawet pomiędzy modułami.

Jedyna znana mi sytuacja gdzie goto może mieć przewagę wydajnościową to tzw. computed goto, wykorzystywane przy pisaniu interpreterów.
Ale na nowoczesnych prockach ta przewaga i tak jest niewielka: https://hal.inria.fr/hal-01100647/document

0

Są jeszcze inne ficzery, pozwalające programiście kontrolować branchowanie. Np. w C++ 20 coś takiego jak atrybuty [[likely]] i [[unlikely]] — ciekawe jak kompilator optymalizuje tak oznaczone skoki.

0

@furious programming:

a tego likely/unlikely nie uzywali w kernelu juz wiele, wiele lat?

0

Raczej nie, bo z tego co wyczytałem, jest to świeży ficzer i może jeszcze nie być w pełni zaimplementowany. W każdym razie ciekawe jestem jakie rozkazy są generowane, że wpływa to na branch predictor.

1

@furious programming:

ty piszesz o cpp, a ja o kernelu który jest w C

https://kernelnewbies.org/FAQ/LikelyUnlikely

In Linux kernel code, one often find calls to likely() and unlikely(), in conditions, like :

bvl = bvec_alloc(gfp_mask, nr_iovecs, &idx);
if (unlikely(!bvl)) {
  mempool_free(bio, bio_pool);
  bio = NULL;
  goto out;
}

In fact, these functions are hints for the compiler that allows it to correctly optimize the branch, by knowing which is the likeliest one. The definitions of these macros, found in include/linux/compiler.h are the following :

#define likely(x)       __builtin_expect(!!(x), 1)
#define unlikely(x)     __builtin_expect(!!(x), 0)
1

Zastanawiam się często czy osoby podpierajace się tekstem Dijkstry w swym odrzucaniu goto w ogóle ten tekst czytały, zwłaszcza końcówkę.
Ciekawe byłoby też zbadanie czy ludzie odrzucajacy goto odrzucają też mechanizm jakim są wyjatki i czy odrzucają je z podobnych powodów co goto.

0

A ja się zastanawiam czy są jakieś języki młodsze niż Java które mają goto? Widzę że C# ma. Czy rozmowa zamyka się tylko w C, C++, C# i Pascal?
Bo jeśli dobrze widze to niskopoziomowy Rust i Zig nie mają goto. Nie mówiąc o wysokopoziomowych językach jak Haskell

0

Są, jest ich trochę.

1
Satanistyczny Awatar napisał(a):

Są, jest ich trochę.

WIem, ale nie powiem :D Taka rozmowa tu na forum :P Aż czata spytam :D

Kilka języków programowania, które mają instrukcje "goto" to:

Assembly (język asemblerowy)
BASIC
C (choć zaleca się unikanie instrukcji "goto" w C)
C++
COBOL
FORTRAN
Pascal (w niektórych wersjach)
PL/I
PL/SQL
Visual Basic (starsze wersje)
Warto jednak zaznaczyć, że większość nowoczesnych języków programowania nie wspiera instrukcji "goto" ze względu na potencjalne problemy z czytelnością i złożonością kodu oraz trudnością w utrzymaniu. Wiele języków oferuje bardziej zaawansowane konstrukcje sterujące, takie jak pętle, instrukcje warunkowe i funkcje, które są bardziej zalecane do tworzenia strukturalnego i czytelnego kodu.

No, ale nie takie było moje oryginalne pytanie. Bo pytałem o młodsze niż Java i tu już czat popłynął totalnie bo wymienił Rust, Swift, Kotlin, D, Nim. A z tego co widzę w internecie to tylko D ma goto i jest faktycznie młodszy od Javy. Czyli mamy na razie dwa język z goto młodsze od Javy, C# i D

3

Jak koniecznie odczuwasz potrzebę bycia poinformowanym o konkretnych przykładach to przykładowo ten słynny Google Go jest najbardziej "mainstreamowy". Zapewne nie inetersują cie assembly languages bo tam obecność "jmp" zaskoczenia raczej nie wywołuje. Namnożyło się też mutacji/dialektów i "wannabe następców" C w których goto się znajduje. Był śmiechowy przypadek języka Zig, w którego issues na githubie mozna znaleźć "Remove goto" https://github.com/ziglang/zig/issues/630 Jest też NekoVM https://nekovm.org/specs/labels-and-gotos/

Zaś do tych co nie załapali o co chodziło z wyjatkami - wyjątki w większości obecnych języków mają cechę którą Dijkstra krytykował - skoki nielokalne.

0
Satanistyczny Awatar napisał(a):

Był śmiechowy przypadek języka Zig, w którego issues na githubie mozna znaleźć "Remove goto" https://github.com/ziglang/zig/issues/630

No i z Ziga goto usuneli:

Some C constructs cannot be translated to Zig - for example, goto, structs with bitfields, and token-pasting macros. Zig employs demotion to allow translation to continue in the face of non-translatable entities.

https://ziglang.org/documentation/master/#toc-Translation-failures

1
Satanistyczny Awatar napisał(a):

Był śmiechowy przypadek języka Zig, w którego issues na githubie mozna znaleźć "Remove goto" https://github.com/ziglang/zig/issues/630 Jest też NekoVM https://nekovm.org/specs/labels-and-gotos/

Przeczytałem kawałek i odnoszę wrażenie, że goto-fobia jest obecna. W każdym języku przecież znajdują się konstrukcje i ficzery, z których ktoś korzystać nie będzie chciał. Jeśli któryś mi się nie podoba to po prostu go nie używam, ale dowiaduję się jak on działa, żeby rozumieć cudzy kod. Tymczasem wielu preferuje inne podejście — ficzer wydaje mi się zły, ktoś tam na niego narzekał, więc forsujmy jego usunięcie, a najlepiej zamieńmy na inną, „more Zig-like way”. Zig jest młodziutki, więc taka akcja przejdzie, ale w dojrzałym języku już nie bardzo — bo kompatybilność wsteczna.

Zresztą ten Zig to kolejny potworek, z przekombinowaną składnią — nie widzę podstaw do ekscytacji.

Zaś do tych co nie załapali o co chodziło z wyjatkami - wyjątki w większości obecnych języków mają cechę którą Dijkstra krytykował - skoki nielokalne.

Jeśli porównać wyjątki do goto, to są one zdecydowanie gorsze — zaciemniają przepływ sterowania (skoki o dowolnym zasięgu, pomiędzy funkcjami), wymuszają mnożenie konstrukcji try finally i try except, obniżają wydajność kodu wynikowego. A już szczególnym przypadkiem ”wyjątkowej” patologii są te rzucane przez konstruktory klas, które wymagają nadźgania jeszcze większych drabinek try. Wyjątek może polecieć gdziekolwiek (nawet do samego spodu call stacku) i nigdy nie wiadomo skąd może się wziąć, bo przecież na pierwszy rzut oka nie widać czy dana metoda którejś klasy może rzucić wyjątkiem czy nie.

A goto? Proste, logiczne, wszechmocne i czytelne — masz go w obrębie wzroku, widzisz dokładnie gdzie są skoki i w jakich sytuacjach, łatwo prześledzić przepływ sterowania, wszystko jest jawne. To praktycznie to samo co śledzenie wywołań funkcji, tyle że bez wywoływania funkcji, bo skoki są lokalne i operują na tych samych danych (zmienne lokalne, parametry funkcji). Jeśli komuś problem sprawia analiza funkcji zawierającej kilka etykiet i goto, to i będzie miał problem z analizą choćby funkcji z pętlami — tam też są skoki, trzeba śledzić iteratory, continue to również jmp, tak samo jak break. Już nie wspominam o funkcjach anonimowych/lambdach i innych ficzerach.

2

jeszcze z wyjatkami trafiają sie tacy geniusze ktrórzy zaklładają że żłapany wyjątek jest właśnie tym czego oczkiwali w danym momencie. Łapią dowolny przeetwarzaja tak jakby złapali ten którego sie spodziewali i kończą obsługę wyjątku. Czyli mechanizm wyjątków rozpier* w drobny mak

0 użytkowników online, w tym zalogowanych: 0, gości: 0