Pytanie początkującego - wskazanie błędów koncepcyjnych itp.

0

Witam, uczę się Javy i ponieważ nie mam doświadczenia w programowaniu, to mam do Was pewną prośbę - czy ktoś mógłby zerknąć na mój przykładowy kod poniżej i wypunktować mi błędy jakie popełniam? Czytałem na czym polega programowanie obiektowe, staram się tak robić, ale tak naprawdę nie wiem czy to co robię jest dobrze, a zależy mi, aby nie łapać złych nawyków. Będę wdzięczny też za wskazówki co zmienić, co można zrobić lepiej, o czym poczytać.

package fibonacciSequence;

public class FibonacciSequenceCalculator {

	public static void main(String[] args) {
		
		System.out.println("Ile początkowych liczb z Ciągu Fibonacciego chcesz wyświetlić?");
		Argument argument = new Argument();
		argument.getDataFromUser();
		FibonacciSequence fibSeq = new FibonacciSequence();
		fibSeq.showFibonacciSequence(argument.getNumber());
	}

}
package fibonacciSequence;

import java.io.*;

public class Argument {
	
	private int number;
	boolean isCorrect;
	
	public void getDataFromUser() {
		BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
		isCorrect = true;
		do {
			try {
				number = Integer.parseInt(input.readLine());
				checkNumber(number);
				isCorrect = true;
				input.close();
			}
			catch(NumberFormatException e) {
				System.out.println("Niepoprawne dane!");
				isCorrect = false;
			}
			catch(IllegalArgumentException e) {
				System.out.println("Liczba musi być większa od 0!");
				isCorrect = false;
			}
			catch(IOException e) {
				System.out.println("Błąd odczytu danych!");
				isCorrect = false;
			}
		}while(!isCorrect);
	}
	
	private void checkNumber(int number) throws IllegalArgumentException{
		if(number < 1) {
			throw new IllegalArgumentException();
		}
	}
	
	public int getNumber() {
		
		return number;
	}
}
package fibonacciSequence;

public class FibonacciSequence {
	
	long[] sequence;
	
	public void showFibonacciSequence(int number) {
		
		if(number == 1) {
			System.out.print(0);
		}
		else if(number < 94) {
			calculateFibonacciSequence(number);
			showResults();
		}
		else {
			showWarning();
		}
	}
	
	private void calculateFibonacciSequence(int number) {
		
		sequence = new long[number];
		sequence[0] = 0;
		sequence[1] = 1;
		for(int i = 2; i < sequence.length; i++) {
			sequence[i] = sequence[i-1] + sequence[i-2];
		}
	}
	
	private void showResults() {
		
		for(long series : sequence) {
			System.out.print(series + " ");
		}
	}
	
	private void showWarning() {
		System.out.println("To zbyt duży zakres - ostatnie liczby nie będą poprawnie obliczone.");
		System.out.println("Wybierz poprawny zakres (maksymalnie 93 liczby).");
	}
}

Z góry dziękuję!

1

API (publiczne metody) powinno być tak zrobione, żeby nie dawalo się (było trudno) źle użyć.

  1. metod **getNumer ** w argument - zwróci int nawet jak argument nie jest poprawny. Lepiej tu dorzuć exception w tej sytuacji. Runtimowy (po to żeby kolejnego checked nie robić, IllegalStateException pasuje).
  2. Ale w zasadzie to twój Argument jest dziwny - raczje potrzebujesz dwóch klas ArgumentReader.. który na getDataFromUser zwraca Argument.

Mały hint- jeśli metoda zwraca void to pewnie czai się w niej zło (Void Demons). Przemyśłl czy nie da się tego voida jakoś wyeliminować.
Nie zawsze się da, na przykład jeśli masz wysyłkę maila i masz metodę sendMail, to generalnie o ile nie poleci exception (problem z połączeniem) to nie ma co zwrócić, wysyłanie maili jest niejako wysyłaniem w pustkę.

1

A co jest złego w voidach?

3

Void-y są słabo odporne na refaktoring. Skoro nic nie robisz z rezultatem to łatwo coś przestawić w kolejności lub zgubić.
Voidy są kiepskie w testowaniu. Najłatwiej testować dając do funkcji zadany input i sprawdzać output. Void od razu wymusza, że rezultat trzeba brać z innego miejsca ... co znowu utrudnia refaktoring/betonuje kod-design.

Void jest po prostu błędogenny.

W programowaniu imperatywnym, a nawet obiektowym void czasami bywa jedynym naturalnym rozwiązaniem. Dość często można go jednak uniknąć i zwykle wychodzi to designowi na plus. Po prostu warto poszukać czy się da. Podany przez op przykład ładnie to pokazuje.

0

@jarekr000000
Dziękuję, za uwagi.
Poprawiłem program (a przynajmniej tak mi się wydaje!) - teraz metoda getNumber() w klasie Argument musi (?) zwrócić inta, ponieważ jest ustawiony konstruktor z argumentem, ale nie ma domyślnego konstruktora.

Twoje wskazówki i moja lektura kilku przykładów zainspirowały mnie, aby ten program rozbudować o wyświetlanie liczby PI, gdzie użytkownik może wybrać liczbę miejsc po przecinku i... chyba zniosłem kwadratowe jajo.
Niby to coś działa, ale największy problem mam z zamknięciem BufferReader w klasie ArgumentReader - sypie mi się wtedy wyjątek w pętli i nie umiem się tego pozbyć, aby zachować funkcjonalność. Obecnie nie jest zamykana i nie wiem, jak to zrobić.

Tak samo klasa Menu mi się nie podoba przez tego switcha. Czy jest jakaś alternatywa, która będzie wyglądać hmm... "zgrabniej" i da się rozszerzać o nowe opcje?

Druga sprawa, że z trzech klas zrobiłem ich 6 + interfejs i też nie wiem, jak to można odchudzić. No i przede wszystkim - czy taki sposób programowania jest hmm... "poprawny / właściwy"?

Nie będę tu wrzucać kodu, bo wyjdzie z tego pokaźny tasiemiec - całość jest tutaj:

https://github.com/AdrianGalus/excercises-git/tree/master/Calculator/src/calculator

2

Tasiemiec... Java tak ma. Ale da się trochę poprawić:

  1. nie musisz kązdej klasy w osobnym pliku trzymać - MyFunctions i obie impleentacje w osobnych plikach ... spoko, ale cała reszta może być w pliku Calculator.java
    Klasy nie muszą być publiczne.
  2. W Menu.java - to "ponowne użycie" zmiennej **argument1 **jest bez sensu:
int number = argument1.getNumber();
		switch(number) {
		case 1: {
			System.out.println("Ile początkowych liczb z Ciągu Fibonacciego chcesz wyświetlić?");
			argument1 = reader2.getDataFromUser();
			FibonacciSequence fibSeq = new FibonacciSequence();
			fibSeq.showResults(argument1);
			break;

NIe potrzebujesz tam żadnej zmiennej.

		switch(argument1.getNumber()) {
		case 1: {
			System.out.println("Ile początkowych liczb z Ciągu Fibonacciego chcesz wyświetlić?");
			FibonacciSequence fibSeq = new FibonacciSequence();
			fibSeq.showResults( reader2.getDataFromUser());
			break;

  1. Ogólnie to cały ten select można ładniej napisac korzystając z polimorfizmu (MyFunctions).
    Możesz sobie zrobić rejestr pod jaką opcją menu kryje się jaka implementacja MyFunctions (HashMap< Integer, MyFunctions >)
  2. Ja sobie w takim Argumentu czasem odpuszczam getter
public class Argument {
	
	public final int number; //final jest tu ważne
	
	public Argument(int n) {
		
		number = n;
	}
}

I gotowe. Będziesz chciał zrobić getter to sobie dorobisz. W praktce jak to jest jakieś API to nie zawsze warto tak skracać.

  1. Zamknięcie BufferedReader? Po prostu zamknij - jak nie działa, wklej wyjątek.
0

Mógłbyś mi kilka rzeczy rozpisać dokładniej?

  1. Tu spoko, rozumiem i poprawię.
  2. O, tego nie rozumiem. Tam jest drugie wywołanie funkcji getDataUser(), ponieważ jest drugie wczytanie danych z klawiatury. Pierwsze jest do wyboru opcji, drugie do wskazania ile liczb ciągu ma być wyświetlonych/miejsc po przecinku w liczbie PI. Jak to usunę, to pociągnie pierwsze wczytanie z klawiatury i do drugiego "etapu".
  3. O tym zaraz zacznę czytać i zmienię.
  4. Tego też nie rozumiem - jak usunę getter, to jak dostanę się do zmiennej number? I czemu musi być finalna? Jak wówczas przekazać wartość z drugiego wczytania z klawiatury? Poprzez nowy obiekt?
  5. No właśnie nie mogę, bo mam wtedy wyjątek IOException odtwarzany w pętli w nieskończoność. Wiem, że chodzi o dwukrotne użycie funkcji getDataFromUser(), ale nie wiem jak to wyprostować.

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