Warning: Constant WP_DEBUG already defined in /home/vladbutn/public_html/wp-config.php on line 82
Am facut o aplicatie care sa ma ajute sa vad in viitor - Vlad Butnaru

Am facut o aplicatie care sa ma ajute sa vad in viitor

Daca titlul te-a atras – as vrea sa il dezvolt putin: Am incercat in repetate randuri sa fac o aplicatie care sa execute mai multe simulari financiare care sa ma ajute sa inteleg cat ar trebui sa cheltui pe zi pentru a ajunge sa economisesc o anumita suma de bani. Desi unele incercari au fost mai reusite decat altele si au avut rezultate apropiate de adevar, am ales sa descriu putin ideea de baza pentru cei care vor sa invete Java prin exemple practice.

Concept

Aplicatia pe care vreau sa o fac trebuie sa aiba 2 mari functionalitati:

  1. Sa poata sa imi spuna cati bani voi avea la finalul fiecarei luni, incepand de astazi, pana la data pe care o aleg eu.
  2. Sa poata sa imi spuna cati bani pot cheltui in fiecare zi pentru a ajunge la o anumita suma economisita la o data aleasa de mine.

Pentru ca acestea sa aiba sens, am nevoie de:

  • Balanta totala la momentul curent.
  • Suma de bani castigata la finalul fiecarei luni.
  • Suma fixa de bani pe care o cheltui lunar (rate, facturi, etc).

Arhitectura

Fiind un utilitar destul de light, am ales sa nu-i fac interfata grafica, asa ca am ales sa construiesc o aplicatie de tip CLI (command line interface). Pentru a citi datele, am ales sa imi formez o structura de fisier JSON care sa descrie datele initiale (si pe care aplicatia sa il citeasca atunci cand o pornesc). Cele 2 functionalitati de sus le voi ingloba in 2 comenzi simple, iar calea catre fisierul de configurare o voi da prin comanda initiala.

Avand in vedere datele descrise mai sus, am ales urmatoarea structura de JSON:

{
	"name": "Vlad",
	"balance_ron": 1000,
	"balance_eur": 500,
	"balance_usd": 50
}

Campurile sunt urmatoarele:

Nume campTip de dateDescriere
nameStringnumele fisierului de configurare. In caz ca voi dori sa am mai multe simulari, voi putea sa creez cate fisiere vreau
balance_rondoublebalanta totala a conturilor in moneda RON
balance_eurdoublebalanta totala a conturilor in moneda EUR
balance_usddoublebalanta totala a conturilor in moneda USD

Pssst…: Daca vrei sa inveti Java de la 0, sau vrei sa iti aprofundezi cunostintele, am cursul perfect pentru tine – vezi aici

Mapez JSON-ul acesta in clasa Configuration

import com.google.gson.annotations.SerializedName;


public class Configuration {
	
	@SerializedName("name")
	private String name;
	
	@SerializedName("balance_ron")
	private double balanceRon;
	
	@SerializedName("balance_eur")
	private double balanceEur;
	
	@SerializedName("balance_usd")
	private double balanceUsd;
	
}

Pentru lucrul cu JSON-ul si interfata CLI, am ales urmatoarele dependinte maven:

	<dependencies>
		<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
		<dependency>
			<groupId>com.google.code.gson</groupId>
			<artifactId>gson</artifactId>
			<version>2.8.6</version>
		</dependency>
		<dependency>
			<groupId>commons-cli</groupId>
			<artifactId>commons-cli</artifactId>
			<version>1.4</version>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-math3</artifactId>
			<version>3.6.1</version>
		</dependency>
</dependencies>

Avand in vedere ca acest utilitar nu va primi update-uri (ci le voi include intr-o aplicatie separata), am ales sa includ logica matematica in aceeasi clasa care va manipula si user IO-ul. Folosind libraria commons-cli, pot defini cateva metode standard care sa se ocupe de interactiunea cu utilizatorul:

	/**
	 * Generates application command line options
	 *
	 * @return application <code>Options</code>
	 */
	private Options getOptions() {
		Options options = new Options();

		options.addOption("f", "filename", true, "personal financial data file");
		return options;
	}

Metoda care defineste posibilele optiuni ale utilizatorului atunci cand ruleaza utilitarul. In cazul meu, voi avea nevoie de calea catre fisierul JSON de configurare initiala (cel cu balantele).

/**
	 * Prints application help
	 */
	private void printAppHelp() {

		Options options = getOptions();

		HelpFormatter formatter = new HelpFormatter();
		formatter.printHelp("JavaStatsEx", options, true);
	}

Metoda care va afisa help-ul atunci cand utilizatorul va rula aplicatia cu flagul –help sau -h.

/**
	 * Parses application arguments
	 *
	 * @param args application arguments
	 * @return <code>CommandLine</code> which represents a list of application
	 *         arguments.
	 */
	private CommandLine parseArguments(String[] args) {

		Options options = getOptions();
		CommandLine line = null;

		CommandLineParser parser = new DefaultParser();

		try {
			line = parser.parse(options, args);

		} catch (ParseException ex) {

			System.err.println("Failed to parse command line arguments");
			System.err.println(ex.toString());
			printAppHelp();

			System.exit(1);
		}

		return line;
	}

Metoda care va citi datele introduse de utilizator.

/**
	 * Runs the application
	 *
	 * @param args an array of String arguments to be parsed
	 */
	public void run(String[] args) {

		CommandLine line = parseArguments(args);
		if (line.hasOption("filename")) {
			String fileName = line.getOptionValue("filename");
			
			try {
				entryPoint(fileName);
			} catch (java.text.ParseException e) {
				e.printStackTrace();
			}

		} else {
			printAppHelp();
		}
	}

Metoda care va citi comanda initiala de rulare si va apela entryPoint folosind calea catre fisierul de configurare.

private void entryPoint(String configurationFile) throws java.text.ParseException {
		ConsolePrinter.printWelcome();
		ConsolePrinter.readConfiguration(configurationFile);
	
		boolean running = true;
		while (running) {
			ConsolePrinter.printMenu();
			int option = ConsolePrinter.readInt("Pick option: ");

			if (option == 0)
				running = false;

			if (option == 1) {
				String dateString = ConsolePrinter.readDate("Target date: ");
				double spendingsPerDay = ConsolePrinter.readDouble("Average spendings per day:");
				double fixedPayments = ConsolePrinter.readDouble("Average fixed payments per month:");
				double earningsPerMonth = ConsolePrinter.readDouble("Average earnings per month: ");

				DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
				Date date = formatter.parse(dateString);

				LocalDateTime now = LocalDateTime.now();
				LocalDateTime wantedDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();

				String leftAlignFormat = "| %-10s | %-4s |%n";
				System.out.format("+-----------+-----------+%n");
				System.out.format("| Month     | Balance   |%n");
				System.out.format("+-----------+-----------+%n");
				double startingBalance = ObjectStorage.configuration.getBalanceRon();
				for (LocalDateTime d = now; d.isBefore(wantedDate); d = d.plusMonths(1)) {
					startingBalance += (float) (earningsPerMonth - spendingsPerDay * 30 - fixedPayments);
					System.out.format(leftAlignFormat, d.getMonth().toString(), startingBalance + "");

				}
				System.out.format("+------------+---------+%n");

				ConsolePrinter.showLine("At given date, you will have: " + startingBalance + " in your accounts");

			}

			if (option == 2) {
				String dateString = ConsolePrinter.readDate("Target date: ");
				double targetSum = ConsolePrinter.readDouble("Target economies: ");
				double fixedPayments = ConsolePrinter.readDouble("Average fixed payments per month:");
				double earningsPerMonth = ConsolePrinter.readDouble("Average earnings per month: ");

				DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
				Date date = formatter.parse(dateString);

				LocalDateTime now = LocalDateTime.now();
				LocalDateTime wantedDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
				double startingBalance = ObjectStorage.configuration.getBalanceRon();

				long daysBetween = ChronoUnit.DAYS.between(wantedDate, now);
				for (LocalDateTime d = now; d.isBefore(wantedDate); d = d.plusMonths(1)) {
					startingBalance += (float) (earningsPerMonth - fixedPayments);

				}

				double dailySum = (targetSum - startingBalance) / daysBetween;

				ConsolePrinter.showLine("You can spend up to: " + dailySum + " per day");
			}
		}

	}

Metoda care executa logica te calcul pentru cele doua functionalitati. Aici am verificat ce optiune a ales utilizatorul (0, 1 sau 2) si, in functie de alegere, execut scenariul de calcul astfel:

  • Pentru option=0, voi inchide procesul aplicatiei.
  • Pentru option=1, voi cere data target, cheltuielile zilnice, cheltuielile fixe lunare si castigurile lunare, apoi voi calcula suma de bani pe care utilizatorul o va avea in balanta adaugand la balanta initiala numarul de luni * castigul lunar numarul de luni * cheltuileli fixe – numarul de zile * cheltuieli zilnice.
  • Pentru option=2, voi cere data target, suma de bani pe care utilizatorul o vrea in conturi, cheltuieli fixe lunare si castiguri lunare si voi afisa maximul de bani pe care utilizatorul o poate cheltui lunar pentru a avea in conturi suma dorita la data aleasa.

Fiind un proiect opensource, poti gasi sursa aici: https://github.com/vladutbutnaru/finvisor.

Pentru a rula aplicatia, nu trebuie decat sa iti configurezi singur un astfel de JSON, sa rulezi mvn clean install pe proiectul clonat si apoi sa rulezi executabilul jar din folderul target folosind calea corecta catre fisier:

./finvisor.jar -f configuration/vlad.json

Alătură-te conversației

1 comentariu

  1. Wonderful, Vlad. Can you and a function that tells you how to become a millionaire in max 1 week? After, you can use the other 2 functions if you still want. 🤔

Lasă un comentariu

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *