Dart from the ground up

Putting it together: a sales report

A capstone that uses the whole course once: a class holding a record, a getter for the line total, a filter and an ordering built from the calls in chapter eight, and a report printed one line at a time.

Part 12 of 12

Advanced21 min readBack to Courses

The shape of a small program

The record is a class with three fields and a constructor that takes them in order, so each sale carries its own item, quantity and price. A getter works out the line total from two of those fields, and an overridden toString gives the record the text form the report prints.

Keeping the arithmetic in the class means the report never multiplies anything. It asks a sale for its total and gets an answer, and the day the rule changes — a discount, a different rounding — there is one line to change instead of one per report.

One question, one transformation

Each line of the report asks one question of the list. The filtered set is a where; the order is a sort with a comparator that puts the largest total first; the grand total is a fold that starts at zero and adds each line. Every one of them is written where its answer is used, so the report reads from the top down.

The loop is only there to print. Keeping the questions apart from the printing is what makes a file of any size readable: another question tomorrow is one more line above the report, and a change of wording is one line inside it.

Where to go next, and one habit to keep

From here the language opens out: Flutter for phones and desktop apps, async and await for work that waits on a network, and the library reference for the calls this course had no room for. Each of them is the same language you have been writing, with more of it in one place.

The habit worth carrying is the one this file is built on. Values live in a class that can explain itself, questions about a collection are asked with named calls rather than handwritten loops, and text is formatted at the moment it is printed. A program arranged that way is one you can still read in six months, which is the only kind of program worth writing.

Three layers, and why they stay apart

The record holds the facts: item, quantity, price. The rules work on those facts: a line total, a filter, an order, a grand total. The report turns the answers into text. Each layer can change without the others, which is what makes the file readable a month later.

Money stays in whole cents until the moment it is printed. The arithmetic is then exact, and the formatting decision — two decimals, a currency mark, a thousands separator — lives on one line instead of being spread through the sums. A total built from rounded prices can disagree with the sum of the rounded lines; keeping cents avoids that.

Named calls replace handwritten loops because each one answers a question in its name. where says which sales survive, sort says the order, fold says the total. A reader can check each answer against the output without simulating a loop.

Reading a report, and changing it safely

Run the report once and read it as a stranger would. Does every line say what it is? Does the total match the lines above it? A report that needs the code beside it to be understood is not finished.

When you add a sale, the filter may include it or exclude it, and either is a chance to check the rule. When you add a column, the total should not move. Those two checks catch most mistakes before a reader does.

Keep the sample data small and fixed. A report that depends on the clock or on random values cannot be compared between runs, and comparing two runs is how you learn whether a change did what you intended.

The filter, the order and the total

void main() {
  final cents = <int>[450, 3200, 1500];
  final big = cents.where((c) => c > 1000).toList();
  big.sort((a, b) => b - a);
  for (final c in big) {
    print("big: " + c.toString());
  }
  final total = cents.fold<int>(0, (s, c) => s + c);
  print("Total: " + total.toString());
}

The report, in one file

Run it and read which sales are left after the filter and in what order they print. Then add a fourth sale, print how many sales there are in total, and add a line that shows the average total in whole cents.

Output
Click "Run" to execute your code

Three report mistakes. Dividing with ~/ when you want a decimal average, which throws away the fraction before it is printed. Sorting the filtered list in place when the original list is used again later, so the next question sees the new order. And formatting each line to two decimals before adding them, which lets rounding drift away from the true total.

Try these next

  • Add a fourth sale, then check that the total changes by exactly its line total.
  • Group the sales by item with a map and print the revenue for each.
  • Print the average line total with two decimals and compare it with the sum of the lines.

Revenue grouped by item

Run it and check that the grouped totals add up to the revenue line. Then add one more Hub sale, and work out the new Hub total and the new grand total before you run it again.

Output
Click "Run" to execute your code

Change one sale and only the report changes: the record holds the values, the named calls ask the questions, and the printing stays at the bottom of the file. That separation is what makes a program of any size possible to read.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

Why is the line total a getter rather than a field?