Instrukcja goto

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

1

Co wy myslicie na temat instrukcji goto jest chyba tylko w języku C,C++ i na studiach mnie uczyli, żeby jej unikać, według mnie trochę błędnie ja programuje w PHP i były przypadki gdzie dobrze było by jej użyć, a w jądrze linuksa jest dużo razy użyta.

3

Z goto to jak z tabelkami w HTMLu. Ludzie unikają ich, bo wszsyscy mówią, że nie wolno, bo to antypattern i umysłowe upodlenie. Oczywiście, że istnieją uzasadnione przypadki użycia goto. Sam nawet niedawno jedno napisałem w pracy. I wbrew pozorom, zwiększyło to czytelność kodu ;) Z drugiej strony, jak widzisz wiele miejsc, gdzie "warto" byłoby go użyć, a nie jedno raz na ruski rok, to coś z pewnością jest nie tak.

0
  • Nie wiem czy akurat goto, ale brakuje mi czasem mechanizmów łatwego opuszczania wielokrotnie zagnieżdżonych pętli.
  • Niedawno walczyłam też z sytuacją, kiedy próbowałam zrobić funkcję, której zadaniem miało być m.in. porzucanie aktualnie wykonywanej funkcji / porzucenie dalszej części kodu. I też się w sumie nie dało. Tzn. dałoby się, ale trza się nakombinować dodając do kodu kolejne klamry i warunki zamiast wcisnąć tam jakiś odpowiednik die().

Ale w 99% przypadków kod bez skoków jest czytelniejszy i bezpieczniejszy.

1

Ktoś kiedyś ogłosił, że goto jest złe i ludzie powtarzają

found 550 'goto' code

@Freja Draco

Ale w 99% przypadków kod bez skoków jest czytelniejszy i bezpieczniejszy.

no tak, bo dodanie goto od razu powoduje, że ziemia się trzęsie pod serwerem.

8

Nie ma uzasadnionego przypadku użycia goto/jmp poza podanymi poniżej wyjątkami:
a) nie umiem jeszcze programować,
b) mój język programowania obsysa,

0

jeszcze jest break i continue

ciekawe co powiecie o tych dwóch demonach :D

3
Freja Draco napisał(a):
  • Nie wiem czy akurat goto, ale brakuje mi czasem mechanizmów łatwego opuszczania wielokrotnie zagnieżdżonych pętli.

Jeśli piszesz o języku JS, to można skorzystać z instrukcji z etykietą i dzięki temu przejść do dowolnego poziomu zagnieżdżenia wzwyż podając nazwę etykiety po instrukcji break lub continue, np:

foo: for (let i = 0; i < 10; i++) {
	for (let j = 0; j < 10; j++) {
		console.log(i);
		
		if (i === j) {
			continue foo;
		}
	}
}
2

Goto używałem w Basicu:) Później nigdy w życiu nie natrafiłem na problem, który by go wymagał. To jest tragedia, jeśli chodzi o debugowanie/utrzymywanie kodu, tak samo jak inne antypaterny.

1

To dość zabawne, ale jak rozmawiałem z ludźmi, którzy od ~20-30 lat siedzą na mainframie, to mi tłumaczyli, że GOTO jest super, ponieważ od razu wiadomo, co się stanie dalej ;)

Przez jakiś czas miałem nawet przyjemność utrzymywać programy liczące sobie więcej lat niż ja, mające po 2000-5000 linii kodu i składające się głównie z GOTO oraz wywołań innych programów składających się z GOTO i wywołań innych podobnych programów. Polecam, nic tak nie uświadamia o konieczności pisania prostego i przejrzystego kodu :)

6

Mnie zastanawia jak według niektórych ten pseudo kod

for (; ; ;)
{
	for (; ; ;)
	{
		if (asdf)
		{
			goto exit;
		}
	}
}

:exit

return *;

jest mniej przewidywalny niż ten

var somecondition = false;
while (!somecondition)
{
	for (; ; ;)
	{
		if (asdf)
		{
			somecondition = true;
			break;
		}
	}
}

return *;

Zresztą nie ma o czym dyskutować. Dobre użycie goto nadal jest dobry mu użyciem goto i się je stosuje, ale zazwyczaj nie w crudach

Jeżeli ktoś ma z tym problem, to niech dalej piszę flagi bo tak wiara nakazuje :D

PS: Napisanie funkcji z użyciem goto =/= napisanie całego softu z goto.

0

Nie piszemy conditionów; taki kod jest, np., zamknięty w funkcji posiadającej instrukcję return.

0

@lion137:

Jest tu 13 goto, zaproponujesz refactor?

https://github.com/dotnet/coreclr/blob/9773db1e7b1acb3ec75c9cc0e36bd62dcbacd6d5/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Signed.D.cs

private static bool TryParseSByteD(ReadOnlySpan<byte> source, out sbyte value, out int bytesConsumed)
{
	if (source.Length < 1)
		goto FalseExit;

	int sign = 1;
	int index = 0;
	int num = source[index];
	if (num == '-')
	{
		sign = -1;
		index++;
		if ((uint)index >= (uint)source.Length)
			goto FalseExit;
		num = source[index];
	}
	else if (num == '+')
	{
		index++;
		if ((uint)index >= (uint)source.Length)
			goto FalseExit;
		num = source[index];
	}

	int answer = 0;

	if (ParserHelpers.IsDigit(num))
	{
		if (num == '0')
		{
			do
			{
				index++;
				if ((uint)index >= (uint)source.Length)
					goto Done;
				num = source[index];
			} while (num == '0');
			if (!ParserHelpers.IsDigit(num))
				goto Done;
		}

		answer = num - '0';
		index++;

		if ((uint)index >= (uint)source.Length)
			goto Done;
		num = source[index];
		if (!ParserHelpers.IsDigit(num))
			goto Done;
		index++;
		answer = 10 * answer + num - '0';

		// Potential overflow
		if ((uint)index >= (uint)source.Length)
			goto Done;
		num = source[index];
		if (!ParserHelpers.IsDigit(num))
			goto Done;
		index++;
		answer = answer * 10 + num - '0';
		// if sign < 0, (-1 * sign + 1) / 2 = 1
		// else, (-1 * sign + 1) / 2 = 0
		if ((uint)answer > (uint)sbyte.MaxValue + (-1 * sign + 1) / 2)
			goto FalseExit; // Overflow

		if ((uint)index >= (uint)source.Length)
			goto Done;
		if (!ParserHelpers.IsDigit(source[index]))
			goto Done;

		// Guaranteed overflow
		goto FalseExit;
	}

FalseExit:
	bytesConsumed = default;
	value = default;
	return false;

Done:
	bytesConsumed = index;
	value = (sbyte)(answer * sign);
	return true;
}
1

Na goto moga pluc tylko ludzie ktorzy od malego programowali na PC x86 (ZX to tez PC).
Instrukcja ta jest bardzo popularna na 8-bitowcach z lat '70-'80 ub. wieku.

Troche mozna jej pewnie znalezc w kodach studentow pierwszego roku pamietajacych te czasy.
Dzisiaj to mozesz ja znalezc moze w Fortranie, Cobolu.
W Javie to raczej dla jaj.

W C mozesz ja pewnie znalezc w kodzie Linuxa, bo jest to klasa oprogramowania tak ciezka ze swiatlo inzynierii ulega tam zakrzywieniu. To co w enterprajs kodzie jest pozadane w kodzie blisko sprzetu moze byc uznane za trolling (oop, wyjatki).

W C++ programuje od kilku ladnych lat i moze widzialem w nim z 5 instrukcji goto.

0

Ja w rzeczywistych programach chciałem użyć raz, bo było czytelniej, ale szef zabronił wrzucać to do repo :( .

1

@WeiXiao: Przykro mi, ale kot uciekł:)

3
Freja Draco napisał(a):
  • Nie wiem czy akurat goto, ale brakuje mi czasem mechanizmów łatwego opuszczania wielokrotnie zagnieżdżonych pętli.

Najprościej, to chyba po prostu nie mieć wielokrotnie zagnieżdżonych pętli, które trzeba opuszczać.

WeiXiao napisał(a):

Mnie zastanawia jak według niektórych ten pseudo kod

No właśnie problem w tym, że to pseudokod. W prawdziwym kodzie jest raczej więcej instrukcji, wywołań więc goto robi więcej zamieszania.

Zresztą nie ma o czym dyskutować. Dobre użycie goto nadal jest dobry mu użyciem goto i się je stosuje, ale zazwyczaj nie w crudach

To coś pewnie jak z dobrym użyciem stanu globalnego albo wielokrotnie zagnieżdżonych pętli. Ja się jednak cieszę, że nie muszę.

2

To coś pewnie jak z dobrym użyciem stanu globalnego albo wielokrotnie zagnieżdżonych pętli. Ja się jednak cieszę, że nie muszę.

To czy tobie się podoba nie ma nic do rzeczy, bo tona softu w swoich corach ma setki jak nie tysiące goto i obstawiam, że nie dla zabawy albo dlatego, że stażysta im pisał obsługę driverów czy coś.

https://github.com/torvalds/linux/search?q=goto&unscoped_q=goto

1

Ale ja nie pisałem, czy mi się podoba, czy nie. Ja pisałem, że się cieszę, że nie muszę.
Rozumiem, że w pewnego rodzaju aplikacjach, używanie goto jest normą, a być może nawet dobrą praktyką. Po prostu nie u mnie.

5
WeiXiao napisał(a):

To czy tobie się podoba nie ma nic do rzeczy, bo tona softu w swoich corach ma setki jak nie tysiące goto i obstawiam, że nie dla zabawy albo dlatego, że stażysta im pisał obsługę driverów czy coś.

https://github.com/torvalds/linux/search?q=goto&unscoped_q=goto

Te wszystkie osy, drivery, biblioteki, banki, systemy kontroli lotów itp. piszą programiści tacy jak my. Błędem jest zakładanie, że ten kod jest dobry. Co najwyżej - przeważnie działa.
Mam tego np. bardzo dobry przykład w javie, gdzie ważne elementy biblioteki standardowej zostałe zrobione na nowo (choćby klasa java.io.File, całe Date itd.), bo design był zrypany na każdym możliwym poziomie. Podobnie krytyczny kod kompilatora (c1,c2..) w jvm jest robiony od nowa.
Jedno i drugie działa w milionach programomów i nawet nie ma mnóstwa błędów, ale jednak to nie był dobry kod/design.
Dodatkowo takie funkcje biblioteczne(jak podałeś) często mają za sobą długą historię. Możliwe, że ten kawałek to napisał jakiś stażysta jeszcze w c w latach 80tych, a potem tylko kopiowali i dopasowywali składnię.
(W latach 80tych taki kod był uznawany za dobry).

2

Bez GOTO na 8 bitach w basic'u niewiele sie dało napisać :) . Osobiście bawiłem się Atari Basic - w którym nie było żadnych udogodnień (funkcje,procedury) - wprawdzie była konstrukcja GOSUB ... RETURN - ale zasadniczo działała jak dwa GOTO. Po przesiadce na PC pierwsze co robiłem to oduczałem :D się używania GOTO w QBASIC'u , który już miał jakieś możliwości proceduralne, by przesiąść się na Turbo Pascal, gdzie i owszem było zdaje się GOTO ale nie spotkałem się było gdzieś używane (w książkach).

2

Jakby ktoś się zastanawiał jak można ładnie użyć goto, to tutaj podrzucam użycie goto w relatywnie młodym kodzie obsługującym chyba request pipeline ASP .NET Cora

https://github.com/aspnet/AspNetCore/blob/master/src/Mvc/Mvc.Core/src/Infrastructure/ResourceInvoker.cs#L272

private Task Next(ref State next, ref Scope scope, ref object? state, ref bool isCompleted)
{
	switch (next)
	{
		case State.InvokeBegin:
			{
				goto case State.AuthorizationBegin;
			}

		case State.AuthorizationBegin:
			{
				_cursor.Reset();
				goto case State.AuthorizationNext;
			}

		case State.AuthorizationNext:
			{
				var current = _cursor.GetNextFilter<IAuthorizationFilter, IAsyncAuthorizationFilter>();
				if (current.FilterAsync != null)
				{
					if (_authorizationContext == null)
					{
						_authorizationContext = new AuthorizationFilterContextSealed(_actionContext, _filters);
					}

					state = current.FilterAsync;
					goto case State.AuthorizationAsyncBegin;
				}
				else if (current.Filter != null)
				{
					if (_authorizationContext == null)
					{
						_authorizationContext = new AuthorizationFilterContextSealed(_actionContext, _filters);
					}

					state = current.Filter;
					goto case State.AuthorizationSync;
				}
				else
				{
					goto case State.AuthorizationEnd;
				}
			}

		case State.AuthorizationAsyncBegin:
			{
				Debug.Assert(state != null);
				Debug.Assert(_authorizationContext != null);

				var filter = (IAsyncAuthorizationFilter)state;
				var authorizationContext = _authorizationContext;

				_diagnosticListener.BeforeOnAuthorizationAsync(authorizationContext, filter);
				_logger.BeforeExecutingMethodOnFilter(
					FilterTypeConstants.AuthorizationFilter,
					nameof(IAsyncAuthorizationFilter.OnAuthorizationAsync),
					filter);

				var task = filter.OnAuthorizationAsync(authorizationContext);
				if (!task.IsCompletedSuccessfully)
				{
					next = State.AuthorizationAsyncEnd;
					return task;
				}

				goto case State.AuthorizationAsyncEnd;
			}

		case State.AuthorizationAsyncEnd:
			{
				Debug.Assert(state != null);
				Debug.Assert(_authorizationContext != null);

				var filter = (IAsyncAuthorizationFilter)state;
				var authorizationContext = _authorizationContext;

				_diagnosticListener.AfterOnAuthorizationAsync(authorizationContext, filter);
				_logger.AfterExecutingMethodOnFilter(
					FilterTypeConstants.AuthorizationFilter,
					nameof(IAsyncAuthorizationFilter.OnAuthorizationAsync),
					filter);

				if (authorizationContext.Result != null)
				{
					goto case State.AuthorizationShortCircuit;
				}

				goto case State.AuthorizationNext;
			}

		case State.AuthorizationSync:
			{
				Debug.Assert(state != null);
				Debug.Assert(_authorizationContext != null);

				var filter = (IAuthorizationFilter)state;
				var authorizationContext = _authorizationContext;

				_diagnosticListener.BeforeOnAuthorization(authorizationContext, filter);
				_logger.BeforeExecutingMethodOnFilter(
					FilterTypeConstants.AuthorizationFilter,
					nameof(IAuthorizationFilter.OnAuthorization),
					filter);

				filter.OnAuthorization(authorizationContext);

				_diagnosticListener.AfterOnAuthorization(authorizationContext, filter);
				_logger.AfterExecutingMethodOnFilter(
					FilterTypeConstants.AuthorizationFilter,
					nameof(IAuthorizationFilter.OnAuthorization),
					filter);

				if (authorizationContext.Result != null)
				{
					goto case State.AuthorizationShortCircuit;
				}

				goto case State.AuthorizationNext;
			}

		case State.AuthorizationShortCircuit:
			{
				Debug.Assert(state != null);
				Debug.Assert(_authorizationContext != null);
				Debug.Assert(_authorizationContext.Result != null);

				_logger.AuthorizationFailure((IFilterMetadata)state);

				// This is a short-circuit - execute relevant result filters + result and complete this invocation.
				isCompleted = true;
				_result = _authorizationContext.Result;
				return InvokeAlwaysRunResultFilters();
			}

		case State.AuthorizationEnd:
			{
				goto case State.ResourceBegin;
			}

		case State.ResourceBegin:
			{
				_cursor.Reset();
				goto case State.ResourceNext;
			}

		case State.ResourceNext:
			{
				var current = _cursor.GetNextFilter<IResourceFilter, IAsyncResourceFilter>();
				if (current.FilterAsync != null)
				{
					if (_resourceExecutingContext == null)
					{
						_resourceExecutingContext = new ResourceExecutingContextSealed(
							_actionContext,
							_filters,
							_valueProviderFactories);
					}

					state = current.FilterAsync;
					goto case State.ResourceAsyncBegin;
				}
				else if (current.Filter != null)
				{
					if (_resourceExecutingContext == null)
					{
						_resourceExecutingContext = new ResourceExecutingContextSealed(
							_actionContext,
							_filters,
							_valueProviderFactories);
					}

					state = current.Filter;
					goto case State.ResourceSyncBegin;
				}
				else
				{
					// All resource filters are currently on the stack - now execute the 'inside'.
					goto case State.ResourceInside;
				}
			}

		case State.ResourceAsyncBegin:
			{
				Debug.Assert(state != null);
				Debug.Assert(_resourceExecutingContext != null);

				var filter = (IAsyncResourceFilter)state;
				var resourceExecutingContext = _resourceExecutingContext;

				_diagnosticListener.BeforeOnResourceExecution(resourceExecutingContext, filter);
				_logger.BeforeExecutingMethodOnFilter(
					FilterTypeConstants.ResourceFilter,
					nameof(IAsyncResourceFilter.OnResourceExecutionAsync),
					filter);

				var task = filter.OnResourceExecutionAsync(resourceExecutingContext, InvokeNextResourceFilterAwaitedAsync);
				if (!task.IsCompletedSuccessfully)
				{
					next = State.ResourceAsyncEnd;
					return task;
				}

				goto case State.ResourceAsyncEnd;
			}

		case State.ResourceAsyncEnd:
			{
				Debug.Assert(state != null);
				Debug.Assert(_resourceExecutingContext != null);

				var filter = (IAsyncResourceFilter)state;
				if (_resourceExecutedContext == null)
				{
					// If we get here then the filter didn't call 'next' indicating a short circuit.
					_resourceExecutedContext = new ResourceExecutedContextSealed(_resourceExecutingContext, _filters)
					{
						Canceled = true,
						Result = _resourceExecutingContext.Result,
					};

					_diagnosticListener.AfterOnResourceExecution(_resourceExecutedContext, filter);
					_logger.AfterExecutingMethodOnFilter(
						FilterTypeConstants.ResourceFilter,
						nameof(IAsyncResourceFilter.OnResourceExecutionAsync),
						filter);

					// A filter could complete a Task without setting a result
					if (_resourceExecutingContext.Result != null)
					{
						goto case State.ResourceShortCircuit;
					}
				}

				goto case State.ResourceEnd;
			}

		case State.ResourceSyncBegin:
			{
				Debug.Assert(state != null);
				Debug.Assert(_resourceExecutingContext != null);

				var filter = (IResourceFilter)state;
				var resourceExecutingContext = _resourceExecutingContext;

				_diagnosticListener.BeforeOnResourceExecuting(resourceExecutingContext, filter);
				_logger.BeforeExecutingMethodOnFilter(
					FilterTypeConstants.ResourceFilter,
					nameof(IResourceFilter.OnResourceExecuting),
					filter);

				filter.OnResourceExecuting(resourceExecutingContext);

				_diagnosticListener.AfterOnResourceExecuting(resourceExecutingContext, filter);
				_logger.AfterExecutingMethodOnFilter(
					FilterTypeConstants.ResourceFilter,
					nameof(IResourceFilter.OnResourceExecuting),
					filter);

				if (resourceExecutingContext.Result != null)
				{
					_resourceExecutedContext = new ResourceExecutedContextSealed(resourceExecutingContext, _filters)
					{
						Canceled = true,
						Result = _resourceExecutingContext.Result,
					};

					goto case State.ResourceShortCircuit;
				}

				var task = InvokeNextResourceFilter();
				if (!task.IsCompletedSuccessfully)
				{
					next = State.ResourceSyncEnd;
					return task;
				}

				goto case State.ResourceSyncEnd;
			}

		case State.ResourceSyncEnd:
			{
				Debug.Assert(state != null);
				Debug.Assert(_resourceExecutingContext != null);
				Debug.Assert(_resourceExecutedContext != null);

				var filter = (IResourceFilter)state;
				var resourceExecutedContext = _resourceExecutedContext;

				_diagnosticListener.BeforeOnResourceExecuted(resourceExecutedContext, filter);
				_logger.BeforeExecutingMethodOnFilter(
					FilterTypeConstants.ResourceFilter,
					nameof(IResourceFilter.OnResourceExecuted),
					filter);

				filter.OnResourceExecuted(resourceExecutedContext);

				_diagnosticListener.AfterOnResourceExecuted(resourceExecutedContext, filter);
				_logger.AfterExecutingMethodOnFilter(
					FilterTypeConstants.ResourceFilter,
					nameof(IResourceFilter.OnResourceExecuted),
					filter);

				goto case State.ResourceEnd;
			}

		case State.ResourceShortCircuit:
			{
				Debug.Assert(state != null);
				Debug.Assert(_resourceExecutingContext != null);
				Debug.Assert(_resourceExecutedContext != null);

				_logger.ResourceFilterShortCircuited((IFilterMetadata)state);

				_result = _resourceExecutingContext.Result;
				var task = InvokeAlwaysRunResultFilters();
				if (!task.IsCompletedSuccessfully)
				{
					next = State.ResourceEnd;
					return task;
				}

				goto case State.ResourceEnd;
			}

		case State.ResourceInside:
			{
				goto case State.ExceptionBegin;
			}

		case State.ExceptionBegin:
			{
				_cursor.Reset();
				goto case State.ExceptionNext;
			}

		case State.ExceptionNext:
			{
				var current = _cursor.GetNextFilter<IExceptionFilter, IAsyncExceptionFilter>();
				if (current.FilterAsync != null)
				{
					state = current.FilterAsync;
					goto case State.ExceptionAsyncBegin;
				}
				else if (current.Filter != null)
				{
					state = current.Filter;
					goto case State.ExceptionSyncBegin;
				}
				else if (scope == Scope.Exception)
				{
					// All exception filters are on the stack already - so execute the 'inside'.
					goto case State.ExceptionInside;
				}
				else
				{
					// There are no exception filters - so jump right to the action.
					Debug.Assert(scope == Scope.Invoker || scope == Scope.Resource);
					goto case State.ActionBegin;
				}
			}

		case State.ExceptionAsyncBegin:
			{
				var task = InvokeNextExceptionFilterAsync();
				if (!task.IsCompletedSuccessfully)
				{
					next = State.ExceptionAsyncResume;
					return task;
				}

				goto case State.ExceptionAsyncResume;
			}

		case State.ExceptionAsyncResume:
			{
				Debug.Assert(state != null);

				var filter = (IAsyncExceptionFilter)state;
				var exceptionContext = _exceptionContext;

				// When we get here we're 'unwinding' the stack of exception filters. If we have an unhandled exception,
				// we'll call the filter. Otherwise there's nothing to do.
				if (exceptionContext?.Exception != null && !exceptionContext.ExceptionHandled)
				{
					_diagnosticListener.BeforeOnExceptionAsync(exceptionContext, filter);
					_logger.BeforeExecutingMethodOnFilter(
						FilterTypeConstants.ExceptionFilter,
						nameof(IAsyncExceptionFilter.OnExceptionAsync),
						filter);

					var task = filter.OnExceptionAsync(exceptionContext);
					if (!task.IsCompletedSuccessfully)
					{
						next = State.ExceptionAsyncEnd;
						return task;
					}

					goto case State.ExceptionAsyncEnd;
				}

				goto case State.ExceptionEnd;
			}

		case State.ExceptionAsyncEnd:
			{
				Debug.Assert(state != null);
				Debug.Assert(_exceptionContext != null);

				var filter = (IAsyncExceptionFilter)state;
				var exceptionContext = _exceptionContext;

				_diagnosticListener.AfterOnExceptionAsync(exceptionContext, filter);
				_logger.AfterExecutingMethodOnFilter(
					FilterTypeConstants.ExceptionFilter,
					nameof(IAsyncExceptionFilter.OnExceptionAsync),
					filter);

				if (exceptionContext.Exception == null || exceptionContext.ExceptionHandled)
				{
					// We don't need to do anything to trigger a short circuit. If there's another
					// exception filter on the stack it will check the same set of conditions
					// and then just skip itself.
					_logger.ExceptionFilterShortCircuited(filter);
				}

				goto case State.ExceptionEnd;
			}

		case State.ExceptionSyncBegin:
			{
				var task = InvokeNextExceptionFilterAsync();
				if (!task.IsCompletedSuccessfully)
				{
					next = State.ExceptionSyncEnd;
					return task;
				}

				goto case State.ExceptionSyncEnd;
			}

		case State.ExceptionSyncEnd:
			{
				Debug.Assert(state != null);

				var filter = (IExceptionFilter)state;
				var exceptionContext = _exceptionContext;

				// When we get here we're 'unwinding' the stack of exception filters. If we have an unhandled exception,
				// we'll call the filter. Otherwise there's nothing to do.
				if (exceptionContext?.Exception != null && !exceptionContext.ExceptionHandled)
				{
					_diagnosticListener.BeforeOnException(exceptionContext, filter);
					_logger.BeforeExecutingMethodOnFilter(
						FilterTypeConstants.ExceptionFilter,
						nameof(IExceptionFilter.OnException),
						filter);

					filter.OnException(exceptionContext);

					_diagnosticListener.AfterOnException(exceptionContext, filter);
					_logger.AfterExecutingMethodOnFilter(
						FilterTypeConstants.ExceptionFilter,
						nameof(IExceptionFilter.OnException),
						filter);

					if (exceptionContext.Exception == null || exceptionContext.ExceptionHandled)
					{
						// We don't need to do anything to trigger a short circuit. If there's another
						// exception filter on the stack it will check the same set of conditions
						// and then just skip itself.
						_logger.ExceptionFilterShortCircuited(filter);
					}
				}

				goto case State.ExceptionEnd;
			}

		case State.ExceptionInside:
			{
				goto case State.ActionBegin;
			}

		case State.ExceptionHandled:
			{
				// We arrive in this state when an exception happened, but was handled by exception filters
				// either by setting ExceptionHandled, or nulling out the Exception or setting a result
				// on the ExceptionContext.
				//
				// We need to execute the result (if any) and then exit gracefully which unwinding Resource
				// filters.

				Debug.Assert(state != null);
				Debug.Assert(_exceptionContext != null);

				if (_exceptionContext.Result == null)
				{
					_exceptionContext.Result = new EmptyResult();
				}

				_result = _exceptionContext.Result;

				var task = InvokeAlwaysRunResultFilters();
				if (!task.IsCompletedSuccessfully)
				{
					next = State.ResourceInsideEnd;
					return task;
				}

				goto case State.ResourceInsideEnd;
			}

		case State.ExceptionEnd:
			{
				var exceptionContext = _exceptionContext;

				if (scope == Scope.Exception)
				{
					isCompleted = true;
					return Task.CompletedTask;
				}

				if (exceptionContext != null)
				{
					if (exceptionContext.Result != null ||
						exceptionContext.Exception == null ||
						exceptionContext.ExceptionHandled)
					{
						goto case State.ExceptionHandled;
					}

					Rethrow(exceptionContext);
					Debug.Fail("unreachable");
				}

				var task = InvokeResultFilters();
				if (!task.IsCompletedSuccessfully)
				{
					next = State.ResourceInsideEnd;
					return task;
				}
				goto case State.ResourceInsideEnd;
			}

		case State.ActionBegin:
			{
				var task = InvokeInnerFilterAsync();
				if (!task.IsCompletedSuccessfully)
				{
					next = State.ActionEnd;
					return task;
				}

				goto case State.ActionEnd;
			}

		case State.ActionEnd:
			{
				if (scope == Scope.Exception)
				{
					// If we're inside an exception filter, let's allow those filters to 'unwind' before
					// the result.
					isCompleted = true;
					return Task.CompletedTask;
				}

				Debug.Assert(scope == Scope.Invoker || scope == Scope.Resource);
				var task = InvokeResultFilters();
				if (!task.IsCompletedSuccessfully)
				{
					next = State.ResourceInsideEnd;
					return task;
				}
				goto case State.ResourceInsideEnd;
			}

		case State.ResourceInsideEnd:
			{
				if (scope == Scope.Resource)
				{
					_resourceExecutedContext = new ResourceExecutedContextSealed(_actionContext, _filters)
					{
						Result = _result,
					};

					goto case State.ResourceEnd;
				}

				goto case State.InvokeEnd;
			}

		case State.ResourceEnd:
			{
				if (scope == Scope.Resource)
				{
					isCompleted = true;
					return Task.CompletedTask;
				}

				Debug.Assert(scope == Scope.Invoker);
				Rethrow(_resourceExecutedContext!);

				goto case State.InvokeEnd;
			}

		case State.InvokeEnd:
			{
				isCompleted = true;
				return Task.CompletedTask;
			}

		default:
			throw new InvalidOperationException();
	}
}
1

Dla mnie goto to normalna konstrukcja języka i jego użytkowanie nie jest niczym nienaturalnym. Jest mnóstwo przypadków, w których skorzystanie z goto pozwala w sposób znaczący skrócić kod i podnieść jego czytelność. Im dłużej czytam źródła SDL-a, tym mam lepsze zdanie na temat goto.

0

@furious programming: absolutnie nie masz racji. Goto nie na racji bytu, przynajmniej w programowaniu obiektowym. SDL jest pewnie w C, tam może ma to sens.

5

@gajusz800: nie wiem co ma do tego programowanie obiektowe. Ogólnie to goto ma sens w C, gdzie obecnie jest to jedyny sensowny sposób na obsługę zasobów w przypadku błędów. Natomiast w "nowoczesnych językach" to jest to archaizm, który praktycznie nigdy nie ma sensu.

0
gajusz800 napisał(a):

@furious programming: absolutnie nie masz racji.

W tym właśnie rzecz, że mam — i nie tylko ja, bo artykuł podlinkowany przez @WeiXiao bardzo dobrze pokazuje, do jakich patologicznych sytuacji dochodzi, kiedy próbuje się na siłę unikać goto. Wbrew pozorom, nie dotyczy to wyłącznie języka C — równie dobrze można to robić w C++, Go czy Pascalach.

goto to narzędzie, a tak jak każde narzędzie, ma swoje zastosowania.

hauleth napisał(a):

Ogólnie to goto ma sens w C, gdzie obecnie jest to jedyny sensowny sposób na obsługę zasobów w przypadku błędów. Natomiast w "nowoczesnych językach" to jest to archaizm, który praktycznie nigdy nie ma sensu.

Jeśli ów błąd nie generuje wyjątku lub wyjątki mogą być wyłączone, to goto ma zastosowanie w dowolnym języku.

Może rozwiniesz trochę swoją wypowiedź i napiszesz coś więcej na temat tych „nowoczesnych języków”? Załóżmy, że chodzi o C# czy Javę (czy jakikolwiek inny język miałeś na myśli) i o kod podobny do tego z przytoczonego artykułu, czyli próba wykonania kilku operacji i jeśli coś pójdzie nie tak, to sprzątamy zasoby. Albo o skoki pomiędzy caseami i innymi blokami, czyli o reużywanie bloków kodu, bez jawnego ich wydzielania do osobnych funkcji (z jakiegokolwiek powodu). Albo o wyskakiwanie z wielokrotnie zagnieżdżonych pętli.

0

@furious programming: to się staje już nudne. Jeśli twój kod w C# albo Javie potrzebuje goto, to znaczy że masz spaghetti. Tak btw, w Javie nie ma goto. Tak samo jak w Scali, Kotlinie, Dart, JavaScript, Pythonie I nikomu goto nie brakuje. Zgadnij czemu?

Na temat takich nisz jak Pascal czy SDL nie będę już się wyzłosliwiać, bo nie o tym temat.

0
gajusz800 napisał(a):

@furious programming: to się staje już nudne.

Tak, nudne się staje pokazywanie praktycznych zastosowań, w których goto jest najlepszym, wybitnie krótkim i czytelnym rozwiązaniem. Tak samo nudne jest czytanie tych ogólnikowych wywodów, nie popartych żadnymi przykładami.

Jeśli twój kod w C# albo Javie potrzebuje goto, to znaczy że masz spaghetti.

No to pokaż w jaki sposób w C# lub w Javie wyskoczyć na zewnątrz zagnieżdżonej pętli, w równie czytelny i efektywny sposób jak z użyciem goto. Do tej pory za każdym razem gdy pada to pytanie, jedyną odpowiedzią jest „to nie używaj zagnieżdżonych pętli” — najbardziej prymitywna ucieczka od przyznania się do błędu.

Zagnieżdżanie pętli to nie jest spaghetti, tak samo jak konieczność finalizacji czegokolwiek.

Tak btw, w Javie nie ma goto.

Nigdzie nie twierdziłem, że jest.

Tak samo jak w Scali, Kotlinie, Dart, JavaScript, Pythonie I nikomu goto nie brakuje. Zgadnij czemu?

Nie mam czasu bawić się w zgadywanki.

Na temat takich nisz jak Pascal czy SDL nie będę już się wyzłosliwiać, bo nie o tym temat.

Nie powinieneś nie dlatego, że to nisze (co jest bzdurą), a dlatego, że nie masz o nich bladego pojęcia.

0

Tak jak już pisałem - masz unikać takich pętli, tzn nie pisać kodu spaghetti, zamiast robić kupę i przykrywać ją pazłotkiem. W 99% przypadków takich rzeczy da się uniknąć i dlatego większość współczesnych języków w ogóle nie ma i nie potrzebuje goto.

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.