Seite 1 von 9

Goto sinnvoll nutzen

Verfasst: 07.04.2014 14:03
von NicTheQuick
Hi Leute,

angesteckt durch bobobos Goto-Affinität, wollte ich hier mal ein kleines Beispiel zum besten geben, das zeigt, wie man Goto sinnvoll nutzen kann.

Dazu möchte ich einen Code-Schnipsel aus meinem aktuellen Projekt als Beispiel nehmen. Der Schnipsel ist zwar ohne das drum herum nicht lauffähig, aber es geht hier in erste Linie um die Idee dahinter. Diese wird übrigens auch häufig in C-Treiber-Code verwendet. Zuletzt gab es da bei Apple einen hübschen Fehler wegen eines Gotos, das zu viel war. Zum Glück kann dieser Fehler in PureBasic wegen der anderen Syntax nicht so einfach auftreten, also könnt ihr beruhigt sein.

Hier also zuerst mal der Code, wie man ihn wohl üblicherweise so schreiben würde:

Code: Alles auswählen

Procedure.i init()
	;Libusb initialisieren
	If (Not USB::init())
		ProcedureReturn #False
	EndIf
	
	;Gerät finden
	*device = USB::findDevice(#idVendor, #idProduct)
	If (Not *device)
		USB::free()
		ProcedureReturn #False
	EndIf
	
	;Konfigurationsmöglichkeit überprüfen
	If (Not USB::checkConfiguration(*device, #nConfiguration, #nInterface, #nAltSetting))
		USB::unuseDevice(*device)
		*device = 0
		USB::free()
		ProcedureReturn #False
	EndIf
	
	;Gerät öffnen
	*handle = USB::openDevice(*device)
	If (Not *handle)
		USB::unuseDevice(*device)
		*device = 0
		USB::free()
		ProcedureReturn #False
	EndIf
	
	If (Not USB::useConfiguration(*handle, #nConfiguration, #nInterface, #nAltSetting))
		USB::closeDevice(*handle)
		*handle = 0
		USB::unuseDevice(*device)
		*device = 0
		USB::free()
		ProcedureReturn #False
	EndIf
	
	ProcedureReturn *handle
EndProcedure
Man sieht hier die ganzen Dopplungen von Aufrufen. 'USB::free()' muss insgesamt 4 mal hin geschrieben werden, 'USB::unuseDevice()' insgesamt 3 mal, dazu kommt noch jeweils die Zuweisung '*handle = 0'. Lediglich bei 'USB::closeDevice()' hält es sich in Grenzen. Das ganze ist natürlich erst mal kein Problem in einem fertigen Programm, da es ja korrekt funktioniert, so wie es soll. Allerdings habe ich während der Entwicklung diese ganzen Zeilen schon öfter hin und her schieben müssen, und dann habe ich natürlich auch Fehler gemacht und irgendwo etwas nicht wieder deinitialisiert, wo ich es hätte tun müssen.

Deswegen habe ich jetzt etwas umgebaut und das ganze sieht so aus:

Code: Alles auswählen

Procedure.i init()
	;Libusb initialisieren
	If (Not USB::init())
		ProcedureReturn #False
	EndIf
	
	;Gerät finden
	*device = USB::findDevice(#idVendor, #idProduct)
	If (Not *device)
		Goto end1
	EndIf
	
	;Konfigurationsmöglichkeit überprüfen
	If (Not USB::checkConfiguration(*device, #nConfiguration, #nInterface, #nAltSetting))
		Goto end2	
	EndIf
	
	;Gerät öffnen
	*handle = USB::openDevice(*device)
	If (Not *handle)
		Goto end2
	EndIf
	
	If (Not USB::useConfiguration(*handle, #nConfiguration, #nInterface, #nAltSetting))
		Goto end3
	EndIf
	
	ProcedureReturn *handle
	
	end3:
	USB::closeDevice(*handle)
	*handle = 0
	
	end2:
	USB::unuseDevice(*device)
	*device = 0
	
	end1:
	USB::free()
	
	ProcedureReturn #False
EndProcedure
Da Labels seit einiger Zeit bei Purebasic lokal sind, kann man in verschiedenen Procedures oder Modulen auch immer wieder die gleichen Labelnamen verwenden ohne sich irgendwie in die Quere zu kommen. Und deswegen ist dieser Code auch möglich geworden. Ich denke man sieht deutlich, welchen Vorteil das ganze hat. Jede Deinitialisierungsfunktion wird nur noch einmal erwähnt und muss nicht kopiert und eingefügt werden. Dadurch kommt es zu weniger Fehlern und mehr Übersichtlichkeit im Hauptteil der Procedure, also alles über 'ProcedureReturn *handle'. Hier wird jede Überprüfung sozusagen zu einem Einzeiler.

Mich hat das Konzept überzeugt und ich nutze es überall, wo es sinnvoll ist.

Konstruktive Kritik nehme ich gerne an und wenn ihr weitere sinnvolle Goto-Einsatzgebiete kennt, dann lasst alle daran teilhaben, indem ihr hier eine entsprechende FAQ-mäßige Antwort drunter setzt.

Re: Goto sinnvoll nutzen

Verfasst: 07.04.2014 16:33
von _sivizius
Ich rate dennoch von GOTO ab, da man das viel einfacher, kompakter und strukturierter machen kann:

Code: Alles auswählen

Procedure.i init()
  If USB::init()
    *device = USB::findDevice(#idVendor, #idProduct)
    If *device
      If USB::checkConfiguration(*device, #nConfiguration, #nInterface, #nAltSetting))
        *handle = USB::openDevice(*device)
        If *handle
          If USB::useConfiguration(*handle, #nConfiguration, #nInterface, #nAltSetting)
            ProcedureReturn *handle
          EndIf
          USB::closeDevice(*handle)
          *handle = 0
        EndIf
      EndIf
      USB::unuseDevice(*device)
    EndIf
    USB::free()
  EndIf
EndProcedure

Re: Goto sinnvoll nutzen

Verfasst: 07.04.2014 17:29
von alter Mann
...also ich nutze GOTO auch im Zusammenhang mit Fehlerbehandlung und Ressourcenfreigabe innerhalb von Schleifen :

Code: Alles auswählen

Procedure A()

s1 = AllocateMemory(...)
s2 = AllocateMemory(...)
s3 = AllocateMemory(...)
...
For i=0 To anz1
  ...
  If fehler : Goto Ende : EndIf

  For j= 0 To anz2
    ...
    If fehler : Goto Ende : EndIf
    ...
  Next j
Next i
Ende:
FreeMemory(s1)
FreeMemory(s2)
FreeMemory(s3)
ProcedureReturn
EndProcedure
Es wird nämlich auch unübersichtlich, wenn man aus verschachtelten Schleifen aussteigen muss oder wenn es bei längerem Quellcode zu viele ProcedureReturn-Anweisungen gibt. Da finde ich das Prinzip "einmal Ressourcen holen - einmal freigeben" und "ein Eingang - ein Ausgang" manchmal angebrachter. Ich nutze GOTO allerdings in 99,9% der Fälle auch immer nur in eine Richtung - in Richtung Ausgang (ProcedureReturn).
Letztendlich ist das aber immer auch eine Geschmacksfrage :wink: .

Re: Goto sinnvoll nutzen

Verfasst: 07.04.2014 17:30
von edel
@_sivizius
Du wirst aber zugeben muessen, dass der Code von Nic leichter zu lesen ist ;)
Fuer die die kein Goto benutzen wollen, gibt es ja noch die Repeatschleife :

Code: Alles auswählen

Procedure.i Init()
	
	repeat
		
		if not USB::init()
			break
		endif		

		*device = USB::findDevice(#idVendor, #idProduct)
		if (not *device)
			USB::free()
			break 
		endIf		

	   	;Konfigurationsmöglichkeit überprüfen
	   	if (not USB::checkConfiguration(*device, #nConfiguration, #nInterface, #nAltSetting))
	    	USB::unuseDevice(*device)
	    	break
	   	endIf		

		;Gerät öffnen
		*handle = USB::openDevice(*device)
		if (not *handle)
			USB::unuseDevice(*device)
			USB::free()
			break
		endIf	   	

		if (not USB::useConfiguration(*handle, #nConfiguration, #nInterface, #nAltSetting))
			USB::closeDevice(*handle)
			USB::unuseDevice(*device)
			USB::free()
			break
		endIf

	until #true

EndProcedure
Sprungmarken fuer Events finde ich auch ganz witzig :D

Code: Alles auswählen

Procedure.i Main()
	
	OpenWindow( 0, #PB_Ignore, #PB_Ignore, 200, 200, "", 
		#PB_Window_SystemMenu|
		#PB_Window_SizeGadget)

	BindEvent(#PB_Event_CloseWindow, ?OnClose)	
	BindEvent(#PB_Event_MoveWindow, ?OnMove)
	BindEvent(#PB_Event_SizeWindow, ?OnSize)

	ButtonGadget( 0, 10, 10, 100, 25, "Button")

	BindgadgetEvent(0, ?OnClick, #PB_EventType_LeftClick)

	while IsWindow( 0 )	
		WaitWindowEvent()		
	wend

	ProcedureReturn 0

	OnClose:		
		CloseWindow(0)
		return

	OnMove:
		debug "OnMove"
		return

	OnSize:
		debug "OnSize"
		return

	OnClick:
		debug "OnClick"
		return

	ProcedureReturn 0
EndProcedure:End Main()	

Re: Goto sinnvoll nutzen

Verfasst: 07.04.2014 17:41
von ts-soft
edel hat geschrieben:Sprungmarken fuer Events finde ich auch ganz witzig :D
Abgesehen davon, das die bei 64-Bit abschmieren :mrgreen:

Re: Goto sinnvoll nutzen

Verfasst: 07.04.2014 19:41
von _sivizius
für events vllt ganz toll...aber da wird eig gosub(! call *address) verwendet...
für verschachtelte schleifen gibt es Break

Code: Alles auswählen

While 1=1
  Repeat
    Break 2 ; verlässt gleich 2 schleifen-ebenen
  Until #False
Wend
Debug "*duck*"
auf jeden Fall sollte man NIE™ Schleifen mit GOTO verlassen!1drölf
GOTO könnte man aber gut nutzen, wenn man ausführbaren Code mit IncludeBinary in eine DataSection aus einer anderen Datei importiert und dahin springt. Aber 1. wenn man das macht, sollte man wohl gleich Assembler nutzen und 2. wofür gibt es libraries?
PS: Sprungmarken finde ich persönlich nur in DataSections sinnvoll. PureBasic ist zwar von Namen her Basic, aber es ist schneller und man kann auf GOTO überall verzichten.

Re: Goto sinnvoll nutzen

Verfasst: 07.04.2014 20:52
von NicTheQuick
_sivizius hat geschrieben:auf jeden Fall sollte man NIE™ Schleifen mit GOTO verlassen!1drölf
Das ist Unsinn. Schleifen (Repeat, While, For) sind nichts anderes als "Goto-Schleifen" mit Abbruchbedingung. Deswegen kann man ohne Probleme von innen heraus an eine beliebige Stelle springen, die im selben Scope ist.
Das beweist auch der ASM-Output von folgendem Code:

Code: Alles auswählen

Define i.i = 1
Repeat
	Debug i
	If i = 10
		Goto ende
	EndIf
	i + 1
ForEver

Debug "NIE"

ende:

Debug "The End"
ASM-Output:

Code: Alles auswählen

; Define i.i = 1
  MOV    qword [v_i],1
; Repeat
_Repeat1:
; Debug i
; If i = 10
  MOV    r15,qword [v_i]
  CMP    r15,10
  JNE   _EndIf3
; Goto ende
  JMP    l_ende
; EndIf
_EndIf3:
; i + 1
  MOV    r15,qword [v_i]
  INC    r15
  MOV    qword [v_i],r15
; ForEver
  JMP   _Repeat1
_Until1:
; 
; Debug "NIE"
; 
; ende:
l_ende:
; 
; Debug "The End"
Ich verschiebe vielleicht diesen Thread lieber doch ins Allgemein-Forum, da es hier zu mehr Diskussionen kommt als geplant. Die Essenz kann man dann immer noch mal neu in die FAQ stellen.

Re: Goto sinnvoll nutzen

Verfasst: 07.04.2014 22:29
von funkheld
Hmmm..., es geht mit GOTO übersichtlicher und Präziser. Ich nehme GOTO gerne...
Auch die C-Progger benehmen sich eigengartig wenn sie GOTO sehen.

Die Compiler setzen dieses GOTO intelligent um ohne Zweifel.

GRuss

Re: Goto sinnvoll nutzen

Verfasst: 08.04.2014 15:41
von _sivizius
Hinweis: Um eine Schleife sicher zu verlassen, sollten Sie immer Break anstelle von Goto verwenden.
siehe hilfe. klar wird Goto vollständig ausgelöst, so das kein Fehler entsteht, wenn man aus Schleifen springt und im Assembler hat man quasi nur Goto, wobei das dann Jump heißt und meist bedingt ist. Aber ich halte es nirgends für notwendig oder sinnvoll. Es ist ÜBERALL ersetzbar durch geeignetere Konstrukte. Wenn etwas zu unübersichtlich wird, muss man gut Kommentieren, Leerzeilen lassen und »;{« bzw. »;}« nutzen, um Code zu verstecken, aber Goto führt bei übermäßigen Gebrauch meiner Erfahrung nach nur zu Spagetti-Code, wie z.B. bei Batch-Code oder Commodore-Basic häufig.

Re: Goto sinnvoll nutzen

Verfasst: 08.04.2014 16:07
von bobobo
Wenn der Bauer nicht schwimmen kann, ist die Badehose dran Schuld!
Hier noch was zur GotoHexenjagd

und hier mal ein Code der Hinweise gibt, warum der intensive Einsatz von GOTO zur Einsparung des einen oder
anderen Atomkraftwerks führen könnte.

Code: Alles auswählen

maxx=90000
i=0
start=ElapsedMilliseconds()
top:
i+1
If i<maxx
  a.s="do_something_"+Str(i)
  Goto top
EndIf
out.s+"Goto:"+Str(ElapsedMilliseconds()-start)+" "
For i=1 To maxx-1
  a.s="do_something_"+Str(i)
Next i
out.s+"For Next:"+Str(ElapsedMilliseconds()-start)+" "
i=1
While i <maxx
  a.s="do_something_"+Str(i)
  i+1
Wend
out.s+"While Wend:"+Str(ElapsedMilliseconds()-start)+" "
i=1
Repeat
  a.s="do_something_"+Str(i)
  i+1
Until i=maxx
out.s+"Repeat Until:"+Str(ElapsedMilliseconds()-start)+" "
Debug out