Wyjście z metody bez wywalania apki

0

Czysto teoretycznie czy da się wyjść z metody (pewnie się da tylko jak to robić w prawidłowy sposób) tak aby to że ona nic nie zwróci nie wywalało programu?
Mamy np. coś takiego:

string nazwa  = Znajdz_nazwe(10);

chciałbym aby program jeśli metoda nie znajdzie nazwy dla 10 nie wywalał przypisania do stringa nazwa tylko wychodził z jakąś informacją.
Czyli pewnie można by było to zrobić dwojako:

  • albo jeśli metoda nic nie znajdzie to do stringa zwrócić jakiś defaultowy string, tylko wtedy muszę znowu sprawdzać czy został zwrócony defaultowy string i wtedy oprogramować dla niego jakieś ruchy
  • albo w samej metodzie oprogramować coś żeby zwróciła jakąś informację i wyszła z programu w taki sposób aby przerwała przypisywanie resultatu do stringu nazwa (jakiś break czy coś?)
0

Można. Co to jest to Result?

0

Nie jestem pewny czy o to Ci chodzi, ale możesz zrobić coś takiego np

static class StringExt
{
    static string Default(this string str, string defVal) => str == null ? defVal : str;
}

string nazwa  = Znajdz_nazwe(10).Default("dupa");

Wtedy przy zwracaniu nulla z metody zwróci zamiast niego jakiś domyślny string. Albo możesz użyć Either z https://louthy.github.io/language-ext/LanguageExt.Core/Monads/Alternative%20Value%20Monads/Either/Either/index.html

O co chodzi z przerwaniem przypisywania?

3

Czytałeś o czym takim jak sterowanie przepływem, instrukcje warunkowe itd?

2

Najprościej w tym przypadku to zwrócić nulla, no chyba ze null jest prawidłową wartością.
Może jakiegoś tupla:

private (string result, bool success) Znajdz(int v)
{
      if (true) //dobre
      {
         return ("costam", true);
      }
         return (null, false);
}

wtedy wołasz:

(string result, bool success)  = Znajdz(10);
if (success) 
 {
    Console.WriteLine(result);
}
4
Qbelek napisał(a):
    static class StringExt
    {
        static string Default(this string str, string defVal) => str == null ? defVal : str;
    }

    string nazwa  = Znajdz_nazwe(10).Default("dupa");

Doprawdy? A może lepiej:

string nazwa  = Znajdz_nazwe(10) ?? "dupa";
1
kzkzg napisał(a):

Najprościej w tym przypadku to zwrócić nulla, no chyba ze null jest prawidłową wartością.

Człowiek pisze o "wywalaniu apliacji" czyli pewnie zwraca nulla.

@aksimoN:

Jest wiele sposobów, NIESTETY nie wyobrażam sobie bez głębszego oswojenia z językiem, atu zwykły null jest problemem. Gorsze lub lepsze, kolejność przypadkowa

  • parametr out
  • Either
  • przemyślenie projektu obiektowego (o którym nie mówisz), połowa z potencjalnych a ELEGANCKICH możliwości by wykonywał zależny kod warunkowo, dla niepustego rezultatu,
0
aksimoN napisał(a):

Czysto teoretycznie czy da się wyjść z metody (pewnie się da tylko jak to robić w prawidłowy sposób) tak aby to że ona nic nie zwróci nie wywalało programu?

Skąd chcesz wyjść:

  1. Z całego programu
  2. Z pętli
  3. Z funkcji
  4. Z funkcji ale "piętro" (lub kilka) wyżej
    Ewentualnie, np zrobić continue w pętli.

private string FindName(int value)
{
  const string[] Names=new String[] {"Ala","Ewa","Maria"};
  if(value<Names.Length) return Names[value];
  throw new ArgumentOutOfRangeException($"FindName: value ({value}) must be less then {Names.Length}");
}

private void Foo()
{
  String str=FindName(10);
}

private void Bar()
{
  try { Foo(); } catch(ArgumentOutOfRangeException e) { MessageAboutError(e.Message()); }
}
2

Czyli masz metodę która może coś znaleźć, i wtedy chcesz coś zrobić z wynikiem, albo może czegoś nie znaleźć - i co wtedy chcesz zrobić?

Czyli w pseudokodzie, chcesz zrobić coś takiego:

Coś coś  = Znajdz_nazwe(10);

if (...) {  // jakieś sprawdzenie czy znalazło nazwę
  return "Znalazłem nazwę " + coś;
} else {
  return "Nie znalazłem nazwy";
}

Więc pytanie co może być tym "czymś"?

  • Możesz zwrócić null, i wtedy musisz zrobić nullchecka: wtedy wartość znaleziona to string, a null oznacza "nie znaleziono". Możesz też zrobić parametr out albo ref, ale tak czy tak będziesz musiał coś do nich wstaw
  • Możesz rzucić i złapać wyjątek: wtedy wartość znaleziona to jest to co zwróci funkcja, a wyjątek oznacza "nie znaleziono"
  • Możesz zwrócić jakiś obiekt jak Optional/Result, którego wartość present to jest wartość znaleziona, a pusty result oznacza "nie znaleziono",
  • Możesz zrobić w obiektowym stylu swoją klasę, np FoundName z metodami isFound() oraz name(); i metoda isFound() zwraca true lub false w zależności od tego czy coś jest znalezione, i jeśli tak to name() zwraca to co jest znalezione

Dodatkowo, niektóre z tych opcji pozwalają skrócić ten zapis, np zamieniając go na ternary ?/: lub null coalesce ??, ale tak czy tak ten if tam będzie (tylko schowany).
Oprócz tego, możesz "zwrócić" wartość używając out albo ref, ale tak czy tak musisz zdecydować co tam "włożyć", albo null, albo instancje klasy.

Bardziej hardcore'owe wyjście, to np przekazanie dwóch strategii do Znajdz_nazwe, przekazując np dwie lambdy lub dwie implementacje jakiegoś interfejsu, np:

Podejście z lambdami

Znajdz_nazwe(10,
  (name) => Console.Write("name found " + name),
  () => Console.Write("name not found"));

Podejście z implementacją interfejsu

interface Listener {
  void nameFound(string name);
  void nameNotFound();
}

Znajdz_nazwe(10, new Listener {
  void nameFound(string name) {
    Console.Write("Name found " + name);
  }

  void nameNotFound() {
    Console.Write("Name not found");
  }
});

Te 6 wyjść to jest niestety wszystko co możesz zrobić, innej opcji oprócz nich chyba nie ma. Ja osobiście chyba wybrałbym opcje z Result albo z własną klasą.

0

No właśnie jak nic nie znajdzie to chcę wyjść do głównego menu czyli poziom wyżej niż przypisanie wyniku funkcji, tak wiem wiem są if'y ale dużo by ich było bo jeden w samej funkcji a drugi po przypisaniu wyniku do stringa. Bo np. jeśli funkcja nic nie znajdzie to mogę zwrócić coś po czym kolejny if będzie wiedział czy obrabiamy ten wynik czy wychodzimy ale wolałbym to ogarniać jakimś przeskokiem do innego miejsca w kodzie. Może już się tak nie pisze programów z jakimiś labelkami dlatego pytam jak to się rozwiązuje prawidłowo

1
aksimoN napisał(a):

No właśnie jak nic nie znajdzie to chcę wyjść do głównego menu czyli poziom wyżej niż przypisanie wyniku funkcji, tak wiem wiem są if'y ale dużo by ich było bo jeden w samej funkcji a drugi po przypisaniu wyniku do stringa. Bo np. jeśli funkcja nic nie znajdzie to mogę zwrócić coś po czym kolejny if będzie wiedział czy obrabiamy ten wynik czy wychodzimy ale wolałbym to ogarniać jakimś przeskokiem do innego miejsca w kodzie. Może już się tak nie pisze programów z jakimiś labelkami dlatego pytam jak to się rozwiązuje prawidłowo

No prawidłowo to się to rozwiązuje poprawnym kontrolą przepływu (np if, return, throw) albo polimorfizmem. Najlepiej by było jakbyś pokazał kod.

0

W głównym menu mam np:

case "7":
  Console.WriteLine("Podaj nazwę aplikacji:");
  string send_appName = Console.ReadLine().Trim();
  Console.WriteLine("Wybrano przesłanie zmian: {0}.", send_appName);
  string[] send_id_and_path_a = find_id_path2("A", send_appName).Result;
  int id_a_send = Int16.Parse(find_id_path2("A", send_appName).Result[0]);
  int id_b_send = Int16.Parse(find_id_path2("B", send_appName).Result[0]);
  if (id_a_send != 0 || id_b_send != 0)
  {
      int issue_number_send = read_issues("A", id_a_send).Result;
      if (issue_number_send != 0)
      {
          string issueIid = issue_number_send.ToString();
          string issueTitle = read_issue("A", id_a_send, issueIid).Result.title;
          var title_pattern = new Regex(@"(Wniosek o przesłanie zmian)[(A-Z ,0-9\._@)]*");
          var title_match = title_pattern.Matches(issueTitle);
          if (title_match.Count == 1)
          {
              //Cos tam robi 
          }
          else Console.WriteLine("Nie znaleziono");
      }
      else
      {
          Console.WriteLine("Brak issue");
      }
  }
  else Console.WriteLine("Nie znalazłem takiej aplikacji");  //to jest else którego nie chciałem dodatkowo pisać
  Console.ReadLine();
  return true;              

a w funkcji (tasku):

static async Task<string[]> find_id_path2(string repository, string AppName) //GET aplication id
{
    List<Project> projects = new List<Project>();
    List<Project> projects_all = new List<Project>();
    Project only_one = new Project();
    string[] id_and_path = new string[2];
    int page = 1;
    int number_projects_pages = 0;
    string next_page;
    do
    {
        //odpytuje i jak znajdzie to dodaje do listy projects_all
    }
    } while (next_page != "");
    if (projects_all.Count > 1)
    {
        list_project(projects_all);
        Console.WriteLine("Podaj ID projektu:");
        string project_nr = Console.ReadLine().Trim();
        Project result = projects.FirstOrDefault(r => r.id == Int16.Parse(project_nr));
        id_and_path[0] = result.id.ToString();
        id_and_path[1] = result.path_with_namespace;
        return id_and_path;
    }
    else
        if (projects_all.Count == 1)
        {
            Project result = projects_all[0];
            id_and_path[0] = result.id.ToString();
            id_and_path[1] = result.path_with_namespace;
            return id_and_path;
        }
        else {                                     //i to jest else którego nie chciałem dodatkowo pisać                      
                id_and_path[0] = ("0");
                id_and_path[1] = ("0");                
                return id_and_path;
             }           
}

Chodziło o to żeby nie trzeba było przypisywać do id_and_path ("0","0") żeby jak funkcja to zwróci nie trzeba było po wyjściu z funkcji oprogramowywać kolejnego przypadku (ostatni else) że dla tego ("0","0") ma napisać że nie znalazł i wraca do głównego menu. Chciałem już ogarnąć to na poziomie tej linii:

string[] send_id_and_path_a = find_id_path2("A", send_appName).Result;

że jak nie uda się funkcji znaleźć i przypisać send_id_and_path to ma wyjść do głównego menu z komentarzem po prostu, wiem że to nie do końca prawidłowe ale mam za dużo miejsc w którym bym musiał do doprogramowywać a to tylko ja używam więc chodzi mi o to żeby było mniej klepania a jednocześnie chętnie się dowiem jak to powinno być prawidłowo rozwiązane :P

ps. z góry przepraszam za pomieszanie z poplątaniem ponieważ ja zazwyczaj robię coś aby działało i nie patrzę na to czy to jest napisane zgodnie ze sztuką ale komentarze uświadamiające mile widziane ;)

2
aksimoN napisał(a):

W głównym menu mam np:

[...]

Chodziło o to żeby nie trzeba było przypisywać do id_and_path ("0","0") żeby jak funkcja to zwróci nie trzeba było po wyjściu z funkcji oprogramowywać kolejnego przypadku (ostatni else) że dla tego ("0","0") ma napisać że nie znalazł i wraca do głównego menu. Chciałem już ogarnąć to na poziomie tej linii:

string[] send_id_and_path_a = find_id_path2("A", send_appName).Result;

że jak nie uda się funkcji znaleźć i przypisać send_id_and_path to ma wyjść do głównego menu z komentarzem po prostu, wiem że to nie do końca prawidłowe ale mam za dużo miejsc w którym bym musiał do doprogramowywać a to tylko ja używam więc chodzi mi o to żeby było mniej klepania a jednocześnie chętnie się dowiem jak to powinno być prawidłowo rozwiązane :P

No to moim zdaniem to wygląda jak za dużo kombinowania w jednym miejscu.

aksimoN napisał(a):

ps. z góry przepraszam za pomieszanie z poplątaniem ponieważ ja zazwyczaj piszę coś aby działało i nie patrzę na to czy to jest robione zgodnie ze sztuką

Niby nie starasz się robić zgodnie ze sztuką, ale to własnie robienie zgodnie ze sztuką sprawiłoby że nie miałbyś tego problemu który teraz masz ;) Czyli ten dodatkowy else którego nie chcesz.

0

czyli właśnie jak bo teraz nie skumałem jak to powinno być żeby nie trzeba było tych dodatkowych dwóch else'ów... jednego w tasku a jednego na koniec case'a

Jak to ogarnę to następnym razem będzie bardziej "ze sztuką"

1
aksimoN napisał(a):

czyli właśnie jak bo teraz nie skumałem jak to powinno być żeby nie trzeba było tych dodatkowych dwóch else'ów... jednego w tasku a jednego na koniec case'a

Jak to ogarnę to następnym razem będzie bardziej "ze sztuką"

No jak ja to czytam, to widzę że z wyżej poziomowej części przekazujesz do niżej poziomowej części aplikacji jakieś dane, ta niżejpoziomowa część je obrabia, i zwraca jakiś default; i potem w wyżej poziomowej części znów obrabiasz te dane z dołu (albo te poprawne, albo te defaultowe); i Ty chcesz nie musieć precyzować tych defaultowych danych w niżej poziomowej części (w find_id_path2()) jak rozumiem, czy tak?

Bo jeśli tak, to moim zdaniem powinieneś zwrócić Task<Result<string[]>> z tej metody. Wtedy możesz zwrócić pustego Result, i potem na nim robić .map() w tym swoim switch'u.

Oprócz tego, kilka tipów:

  • Zamiast string[] zrób obiekt pod te dwie wartości
  • Nie używaj metod statycznych
  • Nie używaj switchów
0

W skrócie... mam główne menu, tam podaje 7 i wchodzę to tego kodu co załączyłem i teraz chodzi o 4tą linię:

string[] send_id_and_path_a = find_id_path2("A", send_appName).Result;

było tak że jeśli funkcja find_id_path2 nie znalazła jakiś dwóch stringów to się wywalało na przypisaniu ich do send_id_and_path_a, ja chciałbym żeby jeśli funkcja nie znajdzie nic to zamiast przypisania wyświetliło jakiś komunikat i wróciło do głównego menu. Czy da się to jakoś skrócić do tej linii?

coś na zasadzie

string[] send_id_and_path_a = find_id_path2("A", send_appName).Result ?? "Nie ma takiej aplikacji" //i tutaj wyjście z case "7"
0
aksimoN napisał(a):

W skrócie... mam główne menu, tam podaje 7 i wchodzę to tego kodu co załączyłem i teraz chodzi o 4tą linię:

string[] send_id_and_path_a = find_id_path2("A", send_appName).Result;

było tak że jeśli funkcja find_id_path2 nie znalazła jakiś dwóch stringów to się wywalało na przypisaniu ich do send_id_and_path_a, ja chciałbym żeby jeśli funkcja nie znajdzie nic to zamiast przypisania wyświetliło jakiś komunikat i wróciło do głównego menu. Czy da się to jakoś skrócić do tej linii?

No samo z siebie się nie da. Musisz to jakoś obkodzić. Albo ifem, albo wyjątkiem, albo polimorfizmem.

Możesz zrobić wyjątek class MissingPathException, rzucić go ze środka find_id_path2(), i złapać poza switchem.

0

sorrki poprawiłem wyżej ale wrzucę tu, nie ma czegoś takiego??

string[] send_id_and_path_a = find_id_path2("A", send_appName).Result ?? "Nie ma takiej aplikacji" //i tutaj wyjście z case "7"
1
aksimoN napisał(a):

sorrki poprawiłem wyżej ale wrzucę tu, nie ma czegoś takiego??

string[] send_id_and_path_a = find_id_path2("A", send_appName).Result ?? "Nie ma takiej aplikacji" //i tutaj wyjście z case "7"

Jeśli jesteś w pętli, to możesz użyć continue.

Ale to wszystko jest strasznie średnie, bo masz zbudowaną taką "strukturę" wielkiej pętli z wielkim switchem, i to nie jest dobre programowanie. Nie powinieneś myśleć o swojej aplikacji w kontekście "tutaj wyjście z case 7", tylko w kontekście tego jak aplikacja ma działać - co powinna konkretnie aplikacja zrobić w momencie, w którym nie znajdziesz tego patha w czymś w czym tam szukasz - i obkodzić to odpowiednio. Ten switch działa na Twoją niekorzyść w takim sensie że ogranicza sposób w jaki myślisz o tej aplikacji. W C# jest masa narzędzi które pozwoliłby Ci osiągnąć to co chcesz, ale nie możesz ich wykorzystać bo się przytwierdziłeś do takiej imperatywnej struktury jak pętla+switch.

0

To jak inaczej zbudować sobie menu wyboru. Wypisuję co można zrobić i z pomocą switch'a pozwalam wybierać użytkownikowi co chce zrobić. Każdy case to mały podprogram. Chciałbym umieć w każdym momencie wyjść z podprogramu żeby wrócić do menu głównego czy jak coś nie wyjdzie czy jak skończę coś robić w podprogramie.

0
aksimoN napisał(a):

To jak inaczej zbudować sobie menu wyboru. Wypisuję co można zrobić i z pomocą switch'a pozwalam wybierać użytkownikowi co chce zrobić. Każdy case to mały podprogram. Chciałbym w każdym umieć w każdym momencie wyjść z podprogramu żeby wrócić do menu głównego czy jak coś nie wyjdzie czy jak skończę coś robić w podprogramie.

No i właśnie o tym mówię, w kontekście zamykania się w struktury. To nie jest żaden "podprogram", sam sobie go tak nazwałeś.

Zbuduj aplikacje metodami, wyjątkami, ifami, pętlami i polimorfizmem, i zobaczysz że Twoje problemy znikną.

Natomiast odpowiadając na Twoje pytanie

aksimoN napisał(a):

sorrki poprawiłem wyżej ale wrzucę tu, nie ma czegoś takiego??

string[] send_id_and_path_a = find_id_path2("A", send_appName).Result ?? "Nie ma takiej aplikacji" //i tutaj wyjście z case "7"

Jedyne co może być propagowane "w górę" przez wszystkie warstwy do switcha w C#, to są wyjątki. Czyli robisz gdzieś throw new ExitFromCase(), a na górze catch (ExitFromCase), ale jak już mówiłem to jest dziwne - to jest Twoja próba obejścia probelmu który sam sobie stworzyłeś. Zbyt imperatywnie myślisz o tej aplikacji, tak się je tworzyło 30 lat temu.

0

No właśnie ja się uczyłem programować 30 lat temu i teraz po prostu próbuje sobie coś ogarnąć a nie chce zaczynać od podstaw... dlatego pytałem na początku ogólnikowo żeby dostać jakieś tipy jak to powinno się robić jako drogowskaz. Fakt że w sumie zapytanie było potem dość konkretne

Ja to tylko tak nazwałem podprogram bo nie umiem się precyzyjnie wysławiać. Mam metody, mam ify i pętle, nie ogarniam wyjątków i polimorfizmu. Myślałem żeby każdy wybór z menu był oddzielną metodą (podprogramem) ale też chyba nie o to Ci chodzi...

W sumie prawie każdy wybór w głównym menu zaczyna się od sprawdzenia czy jest takie send_id_and_path_a jeśli nie ma to ma o tym powiadomić zakończyć i zacząć od nowa...

0
aksimoN napisał(a):

No właśnie ja się uczyłem programować 30 lat temu i teraz po prostu próbuje sobie coś ogarnąć a nie chce zaczynać od podstaw... dlatego pytałem na początku ogólnikowo żeby dostać jakieś tipy jak to powinno się robić jako drogowskaz. Fakt że w sumie zapytanie było potem dość konkretne

Ja to tylko tak nazwałem podprogram bo nie umiem się precyzyjnie wysławiać. Mam metody, mam ify i pętle, nie ogarniam wyjątków i polimorfizmu. Myślałem żeby każdy wybór z menu był oddzielną metodą (podprogramem) ale też chyba nie o to Ci chodzi...

No możesz w taki sposób to zrobić... tylko wtedy w podprogramie musisz napisać else :D

Ty próbujesz dodać jakiś mechanizm "wychodzący", który umie oddać kontrolę przepływu do funkcji wyżej - i w takim podejściu jakie prezentujesz, to nie da się tego prosto zrobić, bo nałożyłeś na swój program zbyt sztywne ramy.

aksimoN napisał(a):

W sumie prawie każdy wybór w głównym menu zaczyna się od sprawdzenia czy jest takie send_id_and_path_a jeśli nie ma to ma o tym powiadomić zakończyć i zacząć od nowa...

No to tutaj się pojawia pytanie czy ta odpowiedzialność wyciągania send_id_and_path_a jest odpowiedzialnością globalną (całościową), czy są zależne od każdego z "podprogramów", i po prostu tak się złożyło żę są wycigane. Jeśli to pierwsze, to mógłbyś pomyśleć o wyciąganiem send_id_and_path_a wczesniej; jeśli tak sie złożyło to zostaw tak jak jest.

Masz gdzieś cały ten kod na githubie?

2
aksimoN napisał(a):

W głównym menu mam np:

case "7":

ciach

Ty nie masz problemu z "wyjściem z metody". Ty masz projekt do zaorania. tego nie da się w ekonomiczny sposb "ulepszyć".

Ale przynajmniej już wiesz, że cała ta brzydka teoria, jakieś polimorfizmy (@Riddle +1), jakieś wzorce, separacja kodu / danych / prezentacji, jakieś książki 700+ stron powaznych autorów ... to nie wymysł idiotów na amerykańskich uniwersytetach, ale konieczność w projektach większej skali.

Kosztowna nauka, ale zawsze nauka, w następnym projekcie będziesz wiedział.

ps. nie włączam się w dyskujsję nt "odpowiedzialności atomowej jednej funkcji", której nazwa składa się z kilku spójników. To nie ma sensu w szerszym kontekście.

0

nie mam tego na githubie, bo tam za dużo danych wrażliwych, ja sobie po prostu oskryptowuję rzeczy które muszę w pracy wykonywać ręcznie przez wyklikiwanie

2
aksimoN napisał(a):

nie mam tego na githubie, bo tam za dużo danych wrażliwych, ja sobie po prostu oskryptowuję rzeczy które muszę w pracy wykonywać ręcznie przez wyklikiwanie

No to moja rada:

  • zamień switcha na if
  • podziel cały kod na małe metody
  • zwróć Result ze swojego find_id_path2

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