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.
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.
- 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.
Ktoś kiedyś ogłosił, że goto jest złe i ludzie powtarzają
@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.
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,
jeszcze jest break
i continue
ciekawe co powiecie o tych dwóch demonach :D
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;
}
}
}
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.
Może warto wrócić do źródeł:
https://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pdf
Kontra: “GOT0 Considered Harmful” Considered Harmful
http://web.archive.org/web/20090320002214/http://www.ecn.purdue.edu/ParaMount/papers/rubin87goto.pdf
Rekontra: On a somewhat disappointing correspondence
https://www.cs.utexas.edu/users/EWD/transcriptions/EWD10xx/EWD1009.html
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 :)
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.
Nie piszemy conditionów; taki kod jest, np., zamknięty w funkcji posiadającej instrukcję return.
Jest tu 13 goto, zaproponujesz refactor?
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;
}
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.
Ja w rzeczywistych programach chciałem użyć raz, bo było czytelniej, ale szef zabronił wrzucać to do repo :( .
@WeiXiao: Przykro mi, ale kot uciekł:)
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ę.
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
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.
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).
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).
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
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();
}
}
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
.
@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.
@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.
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 case
ami 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.
@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.
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.
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.