3
\$\begingroup\$

I'm doing a Java software engineering team project in school and my team has decided on a Finance Tracker application, which contains the main components of Expense, Budget, Statistics, Suggestions and GUIPanels. Currently, the application only allows actions(add, delete, edit) on an expense to revolve around a primary budget and the statistics will also be tied to this primary budget. This is inspired from the git branching model that is available in the command line.

I’m in charge of the Statistics component and I am seeking to get some advice on Code Quality I can improve on. These are some of the classes I am interested to get advice for. Basically these set of classes are supposed to process information given by user input and translate it into Trend Lines(The Java files containing the main JavaFX code is not included here) The reason for possibly a weird implementation especially in the StatsTrendCommand is because I want to allow for optional parameters, so the constructors have to accept a null, and all other classes has to support this design consideration. Some other common concerns include:

1)How to better name the variables in the TrendStatistics class.

Variable of type double include periodicTotalExpenditure

Variables of 1 dimension List type include categoricalExpenses, periodicBudgetLimits, periodicTotalExpenditures, periodicCategoricalExpenditure

Variables of 2 dimension List include periodicCategorisedExpenses

2)What is a nice way to resolve the pass block in TrendStatistics class when both elements is required?

TrendStatistics.java

package seedu.address.model.statistics;

import static java.util.Objects.requireNonNull;

import java.util.ArrayList;
import java.util.List;

import javafx.collections.ObservableList;
import seedu.address.logic.commands.statistics.StatsTrendCommand;
import seedu.address.model.budget.Budget;
import seedu.address.model.budget.BudgetPeriod;
import seedu.address.model.category.Category;
import seedu.address.model.expense.Expense;
import seedu.address.model.expense.Timestamp;

/**
 * Represents the Statistics class that provides a trend line as its Visual Representation method
 */
public class TrendStatistics extends Statistics {

    public static final int INTERVAL_COUNT = 34;

    private Timestamp startDate;

    private Timestamp endDate;

    private Budget primaryBudget;

    private boolean budgetLimitMode;

    private ObservableList<Expense> expenses;

    private List<Timestamp> dates = new ArrayList<>();

    private List<Double> periodicTotalExpenditures = new ArrayList<>();

    private List<Double> periodicBudgetLimits = new ArrayList<>();

    private List<ArrayList<Double>> periodicCategoricalExpenses = new ArrayList<>();

    private TrendStatistics(ObservableList<Expense> expenses, List<Category> validCategories,
                            Timestamp startDate, Timestamp endDate, Budget primaryBudget, boolean isBudgetMode) {

        super(expenses, validCategories);
        this.startDate = startDate;
        this.endDate = endDate;
        this.expenses = getExpenses();
        this.primaryBudget = primaryBudget;
        this.budgetLimitMode = isBudgetMode;
        if (!budgetLimitMode) {
            for (int i = 0; i < validCategories.size(); i++) {
                periodicCategoricalExpenses.add(new ArrayList<>());
            }
        }
    }

    /**
     * Creates 2 trend lines to provide visual aid on the occurrence of total expenses relative to the budget limit
     *
     * @param expenses List of expenses
     * @param validCategories List of allowed categories in MooLah
     * @param startDate The start date of the tracking period
     * @param endDate The end date of the tracking period
     * @param primaryBudget The primary budget whose statistics is taken
     */
    public static TrendStatistics run(ObservableList<Expense> expenses, List<Category> validCategories,
                                      Timestamp startDate, Timestamp endDate,
                                      Budget primaryBudget, boolean isBudgetMode) {

        requireNonNull(primaryBudget);
        boolean isStartPresent = startDate != null;
        boolean isEndPresent = endDate != null;

        if (isStartPresent && isEndPresent) {
            //pass
        } else if (isStartPresent) {
            endDate = startDate.createForwardTimestamp(primaryBudget.getPeriod(),
                    2 * StatsTrendCommand.HALF_OF_PERIOD_NUMBER);
        } else if (isEndPresent) {
            startDate = endDate.createBackwardTimestamp(primaryBudget.getPeriod(),
                    2 * StatsTrendCommand.HALF_OF_PERIOD_NUMBER);
        } else {
            Timestamp centreDate = primaryBudget.getStartDate();
            endDate = centreDate.createForwardTimestamp(primaryBudget.getPeriod(),
                    StatsTrendCommand.HALF_OF_PERIOD_NUMBER);
            startDate = centreDate.createBackwardTimestamp(primaryBudget.getPeriod(),
                    StatsTrendCommand.HALF_OF_PERIOD_NUMBER);
        }

        TrendStatistics statistics = TrendStatistics.verify(expenses, validCategories,
                startDate, endDate, primaryBudget, isBudgetMode);

        statistics.generateTrendLine();
        return statistics;

    }

    /**
     * A method to practise defensive programming
     * @param expenses List of expenses
     * @param validCategories List of allowed categories in MooLah
     * @param startDate The start date of the tracking period
     * @param endDate The end date of the tracking period
     * @param primaryBudget The primary budget whose statistics is taken
     * @param isBudgetMode The condition to determine which mode is used
     */
    private static TrendStatistics verify(ObservableList<Expense> expenses, List<Category> validCategories,
                                             Timestamp startDate, Timestamp endDate,
                                          Budget primaryBudget, boolean isBudgetMode) {
        requireNonNull(startDate);
        requireNonNull(endDate);
        requireNonNull(expenses);
        requireNonNull(validCategories);

        return new TrendStatistics(expenses, validCategories,
                startDate, endDate, primaryBudget, isBudgetMode);
    }

    /**
     * Gathers the data to be used for the elements of the trend line
     */
    private void generateTrendLine() {
        ArrayList<ArrayList<ArrayList<Expense>>> data = new ArrayList<>();


        BudgetPeriod period = primaryBudget.getPeriod();
        Timestamp windowStartDate = primaryBudget.getStartDate();

        Timestamp validDate = findClosestWindowStartDate(startDate, windowStartDate, period);

        int intervalCount = 0;

        while (hasInterval(validDate, endDate, period) && intervalCount < INTERVAL_COUNT) {
            Timestamp localStartDate = validDate;

            Timestamp nextLocalStartDate = localStartDate.plus(period.getPeriod());
            Timestamp localEndDate = nextLocalStartDate.minusDays(1);

            ArrayList<ArrayList<Expense>> periodicCategorisedExpenses =
                    getPeriodicCategorisedExpenses(localStartDate, localEndDate);

            if (budgetLimitMode) {
                double periodicTotalExpenditure = getTotalExpenditure(periodicCategorisedExpenses);
                this.periodicTotalExpenditures.add(periodicTotalExpenditure);
                periodicBudgetLimits.add(primaryBudget.getAmount().getAsDouble());
            } else {
                List<Double> periodicCategoricalExpenditure = getCategoricalExpenditure(periodicCategorisedExpenses);
                flatMapAdd(periodicCategoricalExpenditure);
            }
            dates.add(localStartDate);
            intervalCount++;
            validDate = nextLocalStartDate;

        }

        this.setTitle(String.format("Periodic trendline from %s to %s in the unit of %ss",
                startDate.showDate(), endDate.showDate(),
                period));
    }

    /**
     * Finds the window start date that is closest to the start date of interest
     * @param startDate The start date for the trend line
     * @param windowStartDate The start date for the current winndow
     * @param period The duration of the window
     */
    private Timestamp findClosestWindowStartDate(Timestamp startDate, Timestamp windowStartDate, BudgetPeriod period) {
        Timestamp changedWindowDate = windowStartDate.createBackwardTimestamp(period);
        while (windowStartDate.dateIsAfter(startDate) && changedWindowDate.dateIsAfter(startDate)) {
            windowStartDate = changedWindowDate;
            changedWindowDate = changedWindowDate.createBackwardTimestamp(period);
        }

        return windowStartDate;
    }

    /**
     * Adds the periodic expenditure from each category to its respective category list
     * @param periodicCategoricalExpenditure A list of total expenditure for each category
     */
    private void flatMapAdd(List<Double> periodicCategoricalExpenditure) {
        for (int i = 0; i < periodicCategoricalExpenditure.size(); i++) {
            ArrayList<Double> categoricalExpenses = periodicCategoricalExpenses.get(i);
            categoricalExpenses.add(periodicCategoricalExpenditure.get(i));
        }
    }

    private ArrayList<ArrayList<Expense>> getPeriodicCategorisedExpenses(Timestamp startDate, Timestamp endDate) {
        TabularStatistics statistics = new TabularStatistics(expenses, getValidCategories(), startDate, endDate);
        ArrayList<ArrayList<Expense>> dataWithTotal = statistics.extractRelevantExpenses(startDate, endDate);
        dataWithTotal.remove(dataWithTotal.size() - 1);
        return dataWithTotal;
    }

    private double getExpenditureForCategory(ArrayList<Expense> categorisedExpenses) {
        double total = 0;
        for (Expense expense : categorisedExpenses) {
            total += Double.parseDouble(expense.getPrice().value);
        }
        return total;
    }

    private double getTotalExpenditure(ArrayList<ArrayList<Expense>> data) {
        double total = 0;
        for (ArrayList<Expense> categorisedExpenses : data) {
            total += getExpenditureForCategory(categorisedExpenses);
        }
        return total;
    }

    private List<Double> getCategoricalExpenditure(ArrayList<ArrayList<Expense>> data) {
        ArrayList<Double> result = new ArrayList<>();
        for (ArrayList<Expense> categorisedExpenses : data) {
            result.add(getExpenditureForCategory(categorisedExpenses));
        }
        return result;
    }

    public List<ArrayList<Double>> getPeriodicCategoricalExpenses() {
        return periodicCategoricalExpenses;
    }

    private static boolean hasInterval (Timestamp validDate, Timestamp endDate, BudgetPeriod period) {
        return validDate.isBefore(endDate) && (validDate.plus(period.getPeriod())).isBefore(endDate);
    }

    public List<Timestamp> getDates() {
        return dates;
    }

    public List<Double> getPeriodicTotalExpenditure() {
        return periodicTotalExpenditures;
    }

    public List<Double> getPeriodicBudgetLimits() {
        return periodicBudgetLimits;
    }

    public boolean isBudgetLimitMode() {
        return budgetLimitMode;
    }

    public String toString () {
        return String.format("%s\n%s", getTitle(), getPeriodicTotalExpenditure());
    }
}

Statistics.java

package seedu.address.model.statistics;

import static java.util.Objects.requireNonNull;

import java.util.List;

import javafx.collections.ObservableList;
import seedu.address.logic.commands.statistics.StatsCommand;
import seedu.address.logic.commands.statistics.StatsCompareCommand;
import seedu.address.logic.commands.statistics.StatsTrendCommand;
import seedu.address.model.budget.Budget;
import seedu.address.model.category.Category;
import seedu.address.model.expense.Expense;
import seedu.address.model.expense.Timestamp;



/**
 * Represents the Statistics class in MooLah.
 */
public class Statistics {

    public static final String MESSAGE_CONSTRAINTS_END_DATE = "Start date must be before end date.";

    private ObservableList<Expense> expenses;

    private final List<Category> validCategories;

    private int categorySize;

    private String title;

    /**
     * Creates a Statistics object
     * @param expenses A list of expenses in the current budget
     * @param validCategories A list of tags used among all expenses
     */
    public Statistics(ObservableList<Expense> expenses, List<Category> validCategories) {
        requireNonNull(validCategories);
        requireNonNull(expenses);
        this.expenses = expenses;
        this.validCategories = validCategories;
        this.categorySize = validCategories.size();
    }

    /**
     * Returns the lists of all expenses in the current budget
     */
    public ObservableList<Expense> getExpenses() {
        return expenses;
    }

    public String getTitle() {
        return title;
    }

    List<Category> getValidCategories() {
        return validCategories;
    }

    int getCategorySize() {
        return categorySize;
    }

    /**
     * The main handler method of the Statistics model object to identify what kind of Statistics has to be done
     * with each command word
     * @param expenses List of expenses
     * @param command Command word provided by the user
     * @param date1 First date input given by the user
     * @param date2 Second date input given by the user
     * @param primaryBudget The primary budget whose statistics is taken
     */
    public static Statistics calculateStats(ObservableList<Expense> expenses, String command,
                                            Timestamp date1, Timestamp date2,
                                            Budget primaryBudget, boolean isBudgetMode) {
        requireNonNull(expenses);
        List<Category> validCategories = Category.getValidCategories();
        switch (command) {
        case StatsCommand.COMMAND_WORD:
            return PieChartStatistics.run(expenses, validCategories, date1, date2, primaryBudget);
        case StatsCompareCommand.COMMAND_WORD:
            return TabularStatistics.run(expenses, validCategories, date1, date2, primaryBudget);
        case StatsTrendCommand.COMMAND_WORD:
            return TrendStatistics.run(expenses, validCategories, date1, date2, primaryBudget, isBudgetMode);
        default:
            return null;
        }
    }

    void setTitle(String title) {
        this.title = title;
    }

}

StatsTrendCommand.java

package seedu.address.logic.commands.statistics;
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.core.Messages.MESSAGE_DISPLAY_STATISTICS_WITHOUT_BUDGET;
import static seedu.address.logic.parser.CliSyntax.PREFIX_END_DATE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_MODE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_START_DATE;

import seedu.address.logic.commands.Command;
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
import seedu.address.model.expense.Timestamp;
import seedu.address.model.statistics.Mode;
import seedu.address.model.statistics.TrendStatistics;
import seedu.address.ui.StatsPanel;

/**
 * Calculates and displays statistics
 */
public class StatsTrendCommand extends Command {

    public static final String COMMAND_WORD = "statstrend";

    public static final String MESSAGE_USAGE = COMMAND_WORD
            + ": Shows statistics trends for regular periods between the Start Date and End Date. "
            + "Parameters: "
            + PREFIX_START_DATE + "START_DATE "
            + PREFIX_END_DATE + "END_DATE "
            + PREFIX_MODE + "CATEGORY_OR_BUDGET "
            + "\nExample: " + COMMAND_WORD + " "
            + PREFIX_START_DATE + "11-11-1111 "
            + PREFIX_END_DATE + "12-12-1212 "
            + PREFIX_MODE + "category";

    public static final String MESSAGE_SUCCESS = "Statistics Trend Calculated!";

    public static final int HALF_OF_PERIOD_NUMBER = TrendStatistics.INTERVAL_COUNT / 2;

    private final Timestamp startDate;
    private final Timestamp endDate;
    private final boolean mode;


    private StatsTrendCommand(Timestamp date1, Timestamp date2, Mode mode) {
        requireNonNull(mode);

        this.startDate = date1;
        this.endDate = date2;
        this.mode = mode.isBudgetMode();
    }


    @Override
    protected void validate(Model model) throws CommandException {
        requireNonNull(model);
        if (!model.hasPrimaryBudget()) {
            throw new CommandException(MESSAGE_DISPLAY_STATISTICS_WITHOUT_BUDGET);
        }
    }


    @Override
    public CommandResult execute(Model model) {
        requireNonNull(model);
        model.calculateStatistics(COMMAND_WORD , startDate, endDate, mode);
        return new CommandResult(MESSAGE_SUCCESS, false, false, StatsPanel.PANEL_NAME);
    }

    /**
     * Creates a StatsTrendCommand that only contains a start date
     * @param startDate The start date
     * @param mode The mode specified by the user
     */
    public static StatsTrendCommand createOnlyWithStartDate(Timestamp startDate, Mode mode) {
        requireNonNull(startDate);
        return new StatsTrendCommand(startDate, null, mode);
    }

    /**
     * Creates a StatsTrendCommand that only contains an end date
     * @param endDate The end date
     * @param mode The mode specified by the user
     */
    public static StatsTrendCommand createOnlyWithEndDate(Timestamp endDate, Mode mode) {
        requireNonNull(endDate);
        return new StatsTrendCommand(null, endDate, mode);
    }

    /**
     * Creates a StatsTrendCommand that contains a start date and an end date
     * @param startDate The start date
     * @param endDate The end date
     * @param mode The mode specified by the user
     */
    public static StatsTrendCommand createWithBothDates(Timestamp startDate, Timestamp endDate, Mode mode) {
        requireNonNull(startDate);
        requireNonNull(endDate);
        requireNonNull(mode);
        return new StatsTrendCommand(startDate, endDate, mode);
    }

    /**
     * Creates a StatsTrendCommand that does not contain a start date or end date
     * @param mode The mode specified by the user
     */
    public static StatsTrendCommand createWithNoDate(Mode mode) {
        return new StatsTrendCommand(null, null, mode);
    }


    @Override
    public boolean equals(Object other) {
        return other == this //short circuit if same object
                || (other instanceof StatsTrendCommand // instance of handles nulls
                && startDate.equals(((StatsTrendCommand) other).startDate)
                && endDate.equals(((StatsTrendCommand) other).endDate));
    }
}

StatsTrendCommandParser.java

package seedu.address.logic.parser;

import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.commons.core.Messages.MESSAGE_REPEATED_PREFIX_COMMAND;
import static seedu.address.logic.parser.CliSyntax.PREFIX_END_DATE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_MODE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_START_DATE;

import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;

import seedu.address.logic.commands.statistics.StatsTrendCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.expense.Timestamp;
import seedu.address.model.statistics.Mode;
import seedu.address.model.statistics.Statistics;

/**
 * Parses input arguments and creates a new StatsTrendCommand object
 */
public class StatsTrendCommandParser implements Parser<StatsTrendCommand> {

    public static final List<Prefix> REQUIRED_PREFIXES = Collections.unmodifiableList(List.of(PREFIX_MODE));

    public static final List<Prefix> OPTIONAL_PREFIXES = Collections.unmodifiableList(List.of(
            PREFIX_START_DATE, PREFIX_END_DATE));


    /**
     * Parses the given {@code String} of arguments in the context of the StatsTrendCommand
     * and returns an StatsTrendCommand object for execution.
     * @throws ParseException if the user input does not conform the expected format
     */
    public StatsTrendCommand parse(String args) throws ParseException {
        ArgumentMultimap argMultimap =
                ArgumentTokenizer.tokenize(args, PREFIX_START_DATE, PREFIX_END_DATE, PREFIX_MODE);

        if (!arePrefixesPresent(argMultimap, PREFIX_MODE) || !argMultimap.getPreamble().isEmpty()) {
            throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, StatsTrendCommand.MESSAGE_USAGE));
        }

        if (hasRepeatedPrefixes(argMultimap, PREFIX_START_DATE, PREFIX_END_DATE, PREFIX_MODE)) {
            throw new ParseException(MESSAGE_REPEATED_PREFIX_COMMAND);
        }

        Timestamp startDate = null;
        Timestamp endDate = null;

        boolean isStartPresent = argMultimap.getValue(PREFIX_START_DATE).isPresent();
        boolean isEndPresent = argMultimap.getValue(PREFIX_END_DATE).isPresent();
        Mode mode = ParserUtil.parseMode(argMultimap.getValue(PREFIX_MODE).get());

        if (isStartPresent && isEndPresent) {
            checkStartBeforeEnd(argMultimap);
            startDate = ParserUtil.parseTimestamp(argMultimap.getValue(PREFIX_START_DATE).get());
            endDate = ParserUtil.parseTimestamp(argMultimap.getValue(PREFIX_END_DATE).get());
            return StatsTrendCommand.createWithBothDates(startDate, endDate, mode);
        } else if (isStartPresent) {

            startDate = ParserUtil.parseTimestamp(argMultimap.getValue(PREFIX_START_DATE).get());
            return StatsTrendCommand.createOnlyWithStartDate(startDate, mode);
        } else if (isEndPresent) {
            endDate = ParserUtil.parseTimestamp(argMultimap.getValue(PREFIX_END_DATE).get());
            return StatsTrendCommand.createOnlyWithEndDate(endDate, mode);
        } else {
            return StatsTrendCommand.createWithNoDate(mode);
        }
    }

    /**
     * Returns true if none of the prefixes contains empty {@code Optional} values in the given
     * {@code ArgumentMultimap}.
     */
    private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
        return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
    }

    /**
     * Returns true at least one prefix have more than to one value
     * {@code ArgumentMultiMap}.
     */
    private static boolean hasRepeatedPrefixes(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
        return !(Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getAllValues(prefix).size() <= 1));
    }

    /**
     * Parses the given {@code String} of arguments in the context of the StatsTrendCommand
     * Checks that start date is before the end date of the given {@code ArgumentMultimap}
     *
     * @throws ParseException if the detected start date is after the end date
     */
    private void checkStartBeforeEnd(ArgumentMultimap argMultimap) throws ParseException {
        Timestamp startDate = ParserUtil.parseTimestamp(argMultimap.getValue(PREFIX_START_DATE).get());
        Timestamp endDate = ParserUtil.parseTimestamp(argMultimap.getValue(PREFIX_END_DATE).get());
        if (endDate.isBefore(startDate)) {
            throw new ParseException(Statistics.MESSAGE_CONSTRAINTS_END_DATE);
        }
    }



}

Relevant section of ModelManager.java

@Override
public void calculateStatistics(String command, Timestamp date1, Timestamp date2, boolean isBudgetMode) {
    ObservableList<Expense> primaryBudgetExpenses = getPrimaryBudget().getExpenses();
    Statistics statistics = Statistics.calculateStats(primaryBudgetExpenses, command, date1, date2,
            getPrimaryBudget(), isBudgetMode);
    this.setStatistics(statistics);
}

public Statistics getStatistics() {
    return statistics;
}

public void setStatistics(Statistics statistics) {
    requireNonNull(statistics);
    this.statistics = statistics;
}

I am open to any kind of feedback on code quality, and I’m still testing whether the code works logically. Therefore, I want the code here to be checked not for logic errors(unless there is some fatal flaw you observe) but instead for code quality. Do inform me if more code or some clarification is required for a better review. Thanks in advance.

Update

The code works now, completed the JUnit tests, whose code I won't be showing here until requested.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ You really should verify whether this works correctly before putting it up for review, as per the help center. Please confirm whether you've tested this to any length. \$\endgroup\$
    – Mast
    Commented Nov 4, 2019 at 10:23
  • \$\begingroup\$ This code compiles and works(no exceptions thrown or wrong input) for most identified cases. However, this is definitely still under testing stages so there is no guarantee that it is perfectly correct, so I'm not checking for output correctness but rather quality \$\endgroup\$ Commented Nov 4, 2019 at 10:33
  • \$\begingroup\$ Considering the code doesn't work for all cases you tried, the code isn't ready for review. Please fix the logical problems first, we can help with code quality afterwards. \$\endgroup\$
    – Mast
    Commented Nov 4, 2019 at 12:20
  • 1
    \$\begingroup\$ @Mast I have resolved the logical problems already, it is in good condition to be reviewed. \$\endgroup\$ Commented Nov 4, 2019 at 12:44

1 Answer 1

1
\$\begingroup\$

I have some suggestions to improve readibility of your code: you are using RequireNonNull in several points of your code like below:

requireNonNull(startDate);
requireNonNull(endDate);
requireNonNull(expenses);
requireNonNull(validCategories);

To identify which field is currently null you can add a single message to every RequireNonNull like below:

requireNonNull(startDate,       "startDate must not be null");
requireNonNull(endDate,         "endDate must not be null");
requireNonNull(expenses,        "expenses must not be null");
requireNonNull(validCategories, "validCategories must not be null");

About the method run in your TrendStatistics class it contains an if then like below:

if (isStartPresent && isEndPresent) {
            //pass
} else if (isStartPresent) { //omitted
} else if (isEndPresent) { //omitted
} else { //omitted
}

You can remodulate it excluding the case where both values are true like the code below:

boolean isStartPresent = startDate != null;
boolean isEndPresent = endDate != null;
final BudgetPeriod period = primaryBudget.getPeriod();
final int half = StatsTrendCommand.HALF_OF_PERIOD_NUMBER;
if (!isStartPresent && !isEndPresent) { ...omitted }
if (!isStartPresent && isEndPresent) { ...omitted }
if (isStartPresent && !isEndPresent) { ...omitted }

In your code you use raw types like below:

ArrayList<ArrayList<ArrayList<Expense>>> data = new ArrayList<>();

You can instead use List instead of ArrayList like the line below:

List<List<List<Expense>>> data = new ArrayList<>();

I have seen in your code you are using in your methods lot of parameters (about 8) as in your classes definitions; to improve readibility of code I would suggest you to limit parameters and methods to a maximum of 3 or 4, if you use method names like verify I expect those returning a boolean value and not a new class object, same approach for methods like generateTrendLine that fo me should return an object instead of void.

\$\endgroup\$
2
  • \$\begingroup\$ What would be a good way to name the method verify? That method was meant to do defensive programming, but to use a super constructor, the constructor always goes before the assertion, which makes it useless \$\endgroup\$ Commented Nov 5, 2019 at 16:10
  • \$\begingroup\$ @PrashinJeevaganth, your method returns an instance of object using creation parameters , so something like Factory to indicate the creation of a new object could help to understand what method does. Verify for me means you are examining something returning a boolean. \$\endgroup\$ Commented Nov 5, 2019 at 16:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.