• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Aplikacja do tworzenia Quizów

Object Storage Arubacloud
0 głosów
535 wizyt
pytanie zadane 14 kwietnia 2020 w C# przez Adrian1999 Nałogowiec (34,570 p.)

Cześć wszystkim, przez kilka dni kumuluje sobie, uczę i powtarzam sobie rzeczy z C# chciałbym się was zapytać o czytelność, mojej aplikacji https://github.com/Zilib/Quiz Przy okazji poprosić was o jakieś rady które mogą mi pomóc w programowaniu w tym właśnie o to języku. Projekt na githubie nie posiada readme.me póki co, ponieważ zostało mi dopisać jeszcze dwie funkcje odpowiedzialne za kasowanie oraz edytowanie projektu. A chcę napisać readme.me już w całości ^_^ Dziękuje z góry, wszelką krytykę przyjmę

2 odpowiedzi

+2 głosów
odpowiedź 14 kwietnia 2020 przez gagyn Stary wyjadacz (11,050 p.)
edycja 15 kwietnia 2020 przez gagyn

Pierwsze co się rzuca w oczy to foldery bin i obj oraz .vs. Ich nie powinno być. Dodaj te foldery do gitignore

Jeszcze drobnostka, oczywiście to nie jest błąd, ale masz jakiś powód dla którego korzystasz akurat z .NETFramework v4.7.2? Najnowszy to 4.8, więc warto z niego korzystać. A jeszcze lepiej z .NET Core, ponieważ on powoli przejmuje .NET Framework + .net core 3.0/3.1 posiada obsługę c# 8.0.

Jeszcze ogólnie co do samego kodu.

Widzę, że nazywasz argumenty metod zaczynając od _. Raczej konwencja jest żeby argumenty zapisywać normalnie, z małej litery, a pola prywatne z _ lub bez, tak samo jak argumenty. Oczywiście możesz sobie przyjąć to jak chcesz, jeśli konsekwentnie się będziesz tego trzymać.

#region powinno się raczej rzadko używać, ponieważ klasy i metody powinny być na tyle krótkie i mieć tylko jedną odpowiedzialność, że używanie region staje się niepotrzebne. Osobiście w ogóle nie używam region, albo bardzo rzadko.

Public properties przeważnie zaczyna się z dużej litery, ale to też kwestia indywidualna/teamu.

Co do szczegółów kodu, to jutro przejrzę dokładniej i napiszę tu w komentarzu resztę.

Update:

Na pewno klasy są zbyt długie.

SelectQuiz wydzieliłbym do inne klasy wraz z ShowQuizes.

while (!Int32.TryParse(input, out intInput)
   || --intInput < 0 // Decrement here, right now [1] is first index
   || intInput > Quizes.Count)
{
   Console.Clear();
   Console.WriteLine("Incorrect input! Please choose quiz again!");
   ShowQuizes();
   input = Console.ReadLine();
}

Ten warunek nie podoba mi się, jest stanowczo nieczytelny. Trzeba chwilę czasu poświęcić na zrozumienie kiedy będzie się wykonywać. Wydziel to do metody zwracającej bool np. isCorrectInput i potem użyj tak:

while ( !isCorrectInput(input))

Widzę że masz aż 3 metody CreateNewQuiz (jedna to CreateQuizTest). Wydziel je do osobnej klasy i również w CreateNewQuiz uprość ten warunek while'a wydzielając do nowej metody. Ogólnie możesz porobić więcej metod, tym samym zmniejszając skomplikowanie kodu.

Z tymi properties myślę, czy nie lepiej jako klasę/strukturę zrobić.

public static int numberOfAnswers { get; } = 4;
public static int minQuestions { get; } = 2;
public static int maxQuestions { get; } = 10;
public static int minTitleLength { get; } = 4;
public static int minDescriptionLength { get; } = 15;
        public void LoadGame()
        {
            if (!File.Exists(saveFileName)) // Go back if questions doesn't exist
                return;

            BinaryFormatter binFormat = new BinaryFormatter();
            using (Stream fStream = File.OpenRead(saveFileName))
            {
                try
                {
                    List<Quiz> tempQuzies = (List<Quiz>)binFormat.Deserialize(fStream);
                    try
                    {
                        CheckQuiz(tempQuzies);
                        Quizes = tempQuzies;
                    }
                    catch (ArgumentException)
                    {
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.WriteLine("Plik \"Quizes.dat\" jest uszkodzony!");
                        Console.ReadLine();
                    }
                }
                catch (System.Runtime.Serialization.SerializationException)
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine("Plik \"Quizes.dat\" jest uszkodzony!");
                    Console.ResetColor();
                }
            }
        }

Dlaczego block try catch w bloku try catch?

Najlepiej to postaraj się w ogóle oddzielić kontrolę wyjątków od głównej logiki programu.

Jeszcze drobna uwaga, to raczej używaj var tam gdzie to możliwe zamiast pełnego typu zmiennej. Tak jest przejrzyściej.

        private void CheckQuiz(List<Quiz> _quizes)
        {
            if (_quizes == null 
                || ((from q in _quizes where q.Title == String.Empty 
                     || q.Description == String.Empty select q).Count() > 0))
                throw new ArgumentException("Title or description of question cannot be empty!");


            if ((from q in _quizes where q.Questions == null select q).Count() != 0)
                throw new ArgumentException("Question cannot be null");

            // If every answers 
            if ((from q in _quizes where q.ExistEmptyAnswer() == true select q).Count() != 0)
                throw new ArgumentException("Answers cannot be empty");
        }

trochę dziwnie wyglądają wyrażenia LinQ w tej formie. Lepiej używać normalnych łańcuchów z metod -

_quizes.Where(x => x.ExistEmptyAnswer()).Count()

albo jeszcze lepiej:

_quizes.Any(x => x.ExistEmptyAnswer())

W klasie Quiz:

        public void ShowAllQuestionsAndAnsers()
        {
            Console.Clear();
            
            foreach (Question q in Questions)
            {
                if (q.questionHasAnswer() != 1)
                {
                    Console.WriteLine("Musisz odpowiedzieć na wszystkie pytania!");
                    return;
                }

            }

            ConsoleColor prevBGColor = Console.BackgroundColor;
            ConsoleColor wrongAnswerBGColor = ConsoleColor.Red;
            ConsoleColor correctAnswerBGColor = ConsoleColor.Green;

            ConsoleColor prevFGColor = Console.ForegroundColor;
            ConsoleColor wrongAnswerFGColor = ConsoleColor.Black;
            ConsoleColor correctAnswerFGColor = ConsoleColor.Black;

            // If any answer has no answer, or any answer has more than one answer

            foreach (Question q in Questions)
            {
                Console.ForegroundColor = prevFGColor;
                Console.BackgroundColor = prevBGColor;
                Console.WriteLine($"\t\t**********\t{q.Title.ToUpper()}\t**********\n");
                for (int i = 0; i < q.Answers.Length; i++)
                {
                    // If answer is selected and incorrect
                    if (q.Answers[i].IsSelected // Check only selected answers
                        && !q.Answers[i].SelectedIsCorrect())
                    {
                        Console.BackgroundColor = wrongAnswerBGColor;
                        Console.ForegroundColor = wrongAnswerFGColor;
                        Console.Write($"[{i + 1}]. {q.Answers[i].Title}\n");
                        continue;
                    }
                    // Correct answer
                    if (q.Answers[i].IsSelected // Check only selected answers
                        && q.Answers[i].SelectedIsCorrect())
                    {
                        Console.ForegroundColor = correctAnswerFGColor;
                        Console.BackgroundColor = correctAnswerBGColor;
                        Console.Write($"[{i}]. {q.Answers[i].Title}\n");
                        continue;
                    }
                    Console.ForegroundColor = prevFGColor;
                    Console.BackgroundColor = prevBGColor;
                    Console.Write($"[{i}]. {q.Answers[i].Title}\n");
                }
                // Not selected answer, and not correct
                Console.BackgroundColor = prevFGColor;
                Console.BackgroundColor = prevBGColor;
                Console.WriteLine();
            }
            Console.ResetColor();
        Console.ReadLine();
        }

Źle wygląda ten foreach. Głównie przez te zmiany kolorów. Wydzieliłbym do osobnej klasy całą metodę i podzielił na mniejsze metody każdą odpowiedź.

Na razie tyle.

Jeśli jesteś studentem to polecam zainstalować Resharper od JetBrains, sporo może pomóc przy tworzeniu kodu w c#, a będąc studentem ma się licencję na rok za darmo.

komentarz 15 kwietnia 2020 przez Adrian1999 Nałogowiec (34,570 p.)
Bin i obj to wiem... Ale zauważyłem to dopiero po drugim commicie, także zostawiłem.

Korzystam z Frameworka jedynie dlatego że aktualnie uczę się czystego C# włąśnie w .NET Framework, ale planuje przejście do Core zaraz po ukończeniu książki którą czytam, to stworzyłem w celu utrwalenia wiedzy.

Starałem się unikać konstrukcji z "this", i pokombinowałem troszkę z nazewnictwem, ale po zwróceniu mi uwagi wydaje mi się żę lepiej chyba z this

Public Properties przyjąłem że piszę z dużej litery, gdzie nie zapisywałem?

Jak według Ciebie więc powinna wyglądać klasa Game?
komentarz 15 kwietnia 2020 przez Siemił Mądrala (7,380 p.)

@gagyn, Ze wszystkim co napisałeś się zgodzę poza używanie regionów.

Używanie regionów w funkcji również uważam że nie ma sensu. Jeśli ich tam potrzebujesz to przemyśl jeszcze raz jak dana funkcja powinna wyglądać i co powinna robić. 

Są klasy, które mają dużo pól(np modele widoku) lub funkcji(serwisy) i wtedy używanie regionów aby je z sensem posegregować jak najbardziej jest uzasadnione.

1
komentarz 15 kwietnia 2020 przez gagyn Stary wyjadacz (11,050 p.)
Edytowałem odpowiedź o nowe uwagi.
1
komentarz 15 kwietnia 2020 przez gagyn Stary wyjadacz (11,050 p.)

@Adrian1999 

Bin i obj to wiem... Ale zauważyłem to dopiero po drugim commicie, także zostawiłem.

W takim razie usuń w osobnym commitcie te foldery.

Korzystam z Frameworka jedynie dlatego że aktualnie uczę się czystego C# włąśnie w .NET Framework, ale planuje przejście do Core zaraz po ukończeniu książki którą czytam, to stworzyłem w celu utrwalenia wiedzy.

To nic nie zmienia. O ile nie korzystasz z jakichś wymyślnych bibliotek, to kod będzie w 100% kompatybilny między framework a core.

Public Properties przyjąłem że piszę z dużej litery, gdzie nie zapisywałem?

Chodziło mi o te statyczne properties na początku klasy Game.

komentarz 15 kwietnia 2020 przez gagyn Stary wyjadacz (11,050 p.)

@Siemił Może masz rację, ale ja jeszcze się nie spotkałem z regions, choć nie wykluczam, że w dużych klasa mogą się przydać.

komentarz 15 kwietnia 2020 przez Adrian1999 Nałogowiec (34,570 p.)
Nie jestem studentem, trochę mi się w sumie nie podoba przyczepienie do konstrukcji LINQ, z tego względu, że z tego co ja wiem jest to kwestia kosmetyczna niczym #region, i działa zupełnie tak samo jak operacje z lambdą. O wiele bardziej mi pasuje forma bez używania delegatów z tego względu że jestem przyzwyczajony do formy MySQL. Działanie jest takie same, tylko inaczej wygląda.

Nie rozumiem teraz jednej rzeczy, możliwe że chodzi Ci o oddzielenie wszystkiego na MVC, ale przy tym jak piszesz abym stworzył na wszystko oddzielną klasę to się zastanawiam "Co ta klasa ma sobą reprezentować, ma być to klasa której trzeba tworzyć nowy obiekt by inna klasa dobrze działała?".

Te zmienne statyczne potraktowałem jako główny zbiór zasad i reguł dla każdego quizu, tak jak i napisałem "Public static (config)". Uważasz że wydzielenie tego do kolejnej klasy statycznej która nazywała by się przykładowo "GameConfig" ma sens? Od razu tłumaczę swoje myśli, czemu się uparłem tak na statyczność.
Uparłem się ze względu że jestem początkujący, i moje rozumienie sensu "statyczności" jest takie że zmienne statyczne są umieszczane na sterte, i są umieszczane tam tylko raz. Tworzę zmienną statyczną i te zasady są zawsze niezmienne dla każdej klasy GameConfig.

Dzięki za wszystkie uwagi, to do czego się nie odniosłem uważam za coś co nie ma co komentować a przyjrzeć się analizie.
komentarz 15 kwietnia 2020 przez Adrian1999 Nałogowiec (34,570 p.)
W jednym z dwóch commitów wprowadziłem wstępną zmianę co do uwagi by podzielić klasę na kilka. Czy chodzi Ci mniej więcej o taki efekt? Oraz mam pytanie, co uważasz na temat zastąpienia "wyjątków", w sposób taki jak to zrobiłem w przypadku ostatniego commita.
0 głosów
odpowiedź 15 kwietnia 2020 przez Siemił Mądrala (7,380 p.)
Nie wiem jak długo programujesz, ale napiszę co bym zmienił.

1. Wywaliłbym wszystko co jest statyczne.

2. Rozdzieliłbym widok od funkcjonalności.

3. Oddzielić walidację od funkcjonalności.

Twoje klasy są bardzo sztywno ze sobą powiązane. Będziesz później miał problem ze zmianami gdy aplikacja urośnie.
komentarz 15 kwietnia 2020 przez Adrian1999 Nałogowiec (34,570 p.)
W C# od 2 miesięcy. Teraz będę się odwoływać

1. W jakim celu wywalić statyczne zmienne? Z tego co rozumiem zmienne statyczne, to zmienne które są alokowane jeden raz. Czyli wedle tej zasady stworzyłem ogólny zbiór zasad dla wszystkich powiązanych klas z klasą Quiz.

2. Okej to ma sens, rozumiem MVC

3.  Chodzi o przesunięcie tego co oznaczyłem np "regionem" w oddzielną metodę tak?

Co do sztywności klas, troszkę nie rozumiem. Mógłbyś powiedzieć na jakimś przykładzie?
1
komentarz 15 kwietnia 2020 przez Siemił Mądrala (7,380 p.)
1. Później jakbyś chciał napisać test jednostkowy to będziesz miał problem z takimi klasami, funkcjami czy zmiennymi. Również statyczne stany do których ma dostęp cała aplikacja może być przyczyną różnych błędów trudnych do debugowania. Ale jeśli to do ciebie nie przemawia to sprawdź sobie singleton pattern.

3. Nie, chodzi o to żeby były osobne funkcje które sprawdzają dane, a osobne które je mielą. Większości przypadkach widzę że udało ci się to zachować ale nie wiem czy nie przez przypadek, bo nie wszędzie. Co do regionów w funkcjach to tak jak już pisałem w innym komentarzu. Uważam, że nie mają one sensu. Bo jeśli potrafisz podzielić ciało funkcji na regiony to równie dobrze możesz je przenieś do osobnych funkcji co też będzie bardziej przejrzyste.

Co do podsumowanie. Stan do którego powinieneś dążyć to taki w którym jedna klasa nie wie nic o istnieniu innych klas. Żyje w separacji. A widzę ze w klasie Question używasz bezpośrednio danych klasy Game. Co utrudnia później wprowadzenie zmian czy testowanie tej klasy. Do usuwania tych zależności służą takie techniki jak wstrzykiwanie zależności czy też odwrócenie zależności.

Jak na 2 miesiące to jest naprawdę dobrze.
komentarz 15 kwietnia 2020 przez Adrian1999 Nałogowiec (34,570 p.)
Pozwolę sobie skopiować komentarz ponieważ nie wiem czy się do niego odniosłeś, a nie widzę sensu pisania od nowa pytania do innego kolegi / koleżanki.
"W jednym z dwóch commitów wprowadziłem wstępną zmianę co do uwagi by podzielić klasę na kilka. Czy chodzi Ci mniej więcej o taki efekt? Oraz mam pytanie, co uważasz na temat zastąpienia "wyjątków", w sposób taki jak to zrobiłem w przypadku ostatniego commita."

Wiesz czy przypadek to nie wiem, ogólnie to nie jest skończony projekt. Aktualnie wracam czytać dalej książkę której wiedzę tutaj utwierdzałem. Zamierzam zrobić teraz "test", aplikacja jest nie skończona, i to perfidnie widać. Za dwa tygodnie chcę do niej wrócić i zobaczyć ile problemów mi spowoduje czytanie mojego kodu. I zrefaktoryzować resztę, podzielić na funkcję itp.

Podobne pytania

0 głosów
0 odpowiedzi 269 wizyt
pytanie zadane 26 marca 2020 w C# przez barti911 Nowicjusz (120 p.)
0 głosów
2 odpowiedzi 421 wizyt
pytanie zadane 26 września 2018 w C# przez Sc4red Użytkownik (590 p.)
0 głosów
1 odpowiedź 947 wizyt
pytanie zadane 24 lipca 2018 w C# przez kubekszklany Gaduła (3,190 p.)

92,536 zapytań

141,376 odpowiedzi

319,449 komentarzy

61,920 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...