Object-Oriented Software Engineering

Lecture 7: Implementation · Investigating Mysteries

Video 🔒

  1. The goal isn’t necessarily to solve the mysteries, but to investigate them methodically.
  2. Think out loud. Share your thoughts. Ask questions. When investigating alone, use a rubber duck.
  3. Reproduce the problem on your machine.
  4. Establish what you expected to occur, and what really occurred.
  5. Find a minimal working example. Break the problem in smaller pieces and get rid of the nonessential parts.
  6. Try variations on the problem: different data, different browsers, different tools, different machines, and so forth.
  7. Challenge your assumptions. If you think you know something, come up with an experiment to prove it.

The Mystery of the Magic Island

I was born in an island in Brazil called Florianópolis. Some people call it the Magic Island. Indeed, there seems to be something magic about it. Check the following snippet in the JavaScript Console in Google Chrome:

const islandsInBrazil = [
  "Fernando de Noronha",
  "Ilha do Mel",
  "Florianópolis",
  "Ilha Grande"
];
const islandChoice = prompt("Choose an island in Brazil", "Florianópolis");
if (islandsInBrazil.includes(islandChoice)) {
  alert("You’re right. This is an island in Brazil.");
} else {
  alert("You’re wrong. This is not an island in Brazil.");
}

What’s going on?

More Information

Here’s a video for you to learn more about Unicode.

Different tools interpret strings in different ways:

Chrome: Shows both strings as equal, even when editing and moving the cursor.

Safari: Normalizes the string as you paste it. The example doesn’t work if you paste it into Safari’s developer tools.

IntelliJ: Let you put your cursor in the middle of the character with a combining accent!

Visual Studio Code: Takes two presses of the right arrow to move the cursor over the character with a combining accent.

Some tools to work with strings in JavaScript:

Break a string apart into characters: Array.from().

See the Unicode code point of a character: codePointAt().

Bringing Array.from() and codePointAt() together, we have:

> Array.from("Florianópolis").map(s => s.codePointAt(0))
[70, 108, 111, 114, 105, 97, 110, 111, 769, 112, 111, 108, 105, 115]
> Array.from("Florianópolis").map(s => s.codePointAt(0))
[70, 108, 111, 114, 105, 97, 110, 243, 112, 111, 108, 105, 115]

Normalization: normalize().

The Mystery That Doesn’t Add Up

This Sunday I’ll turn 29 years old. Right now I’d say I’m 28.8 years old, and after this lecture I’ll be a bit older, say 0.1 years older. I think that’ll make me 28.9 years old, but I’m not sure because I’m terrible at arithmetic. I wrote a JavaScript program to check my answer, but it doesn’t seem to work every time. Could this be because I was born on the Magic Island?

alert("Welcome to Leandro’s age calculation checker");
const leandrosCurrentAge = Number(prompt("Leandro’s current age", "28.8"));
const ageIncrementAfterLecture = Number(
  prompt("Age increment after lecture", "0.1")
);
const expectedAgeAfterLecture = Number(
  prompt("Expected age after lecture", "28.9")
);
const ageAfterLecture = leandrosCurrentAge + ageIncrementAfterLecture;
if (ageAfterLecture === expectedAgeAfterLecture) {
  alert("You got the math right!");
} else {
  alert("You got the math wrong!");
}

What’s going on?

More Information

Floating points may be imprecise, but they’re way faster. That’s why we use them.

See https://0.30000000000000004.com for more on this topic.

When writing automated tests, you almost never want to assert that two floating point numbers are equal; you almost always want to assert that they are equal within a given range (what the JUnit documentation calls a delta).

If you don’t want the imprecision of floating points and don’t care about your program being slower, you can use arbitrary precision arithmetic, for example, the BigDecimals in Java and libraries such as decimal.js in JavaScript.

Watch this short talk on other quirks of JavaScript, including things like:

"Leandro " + 1;
"Leandro " - 1;
5 / 0;
0 / 0;
0 ** 0;
[] + [];
[] + {};
({} + []);
({} + {});

const islandsInBrazil = [
  "Fernando de Noronha",
  "Ilha do Mel",
  "Florianópolis",
  "Ilha Grande"
];
islandsInBrazil[1000];

const pieRankings = {
  apple: 9,
  pumpkin: 7,
  raspberry: 10
};
pieRankings.banana;
pieRankings["banana"];

try {
  undefinedVariable;
} catch {}

try {
  pieRankings();
} catch {}

The Mystery of That Which Wasn’t Itself

While I was investigating the problem above, I changed the line that says ageAfterLecture === expectedAgeAfterLecture to ageAfterLecture === ageAfterLecture. Surely ageAfterLecture should be equal to itself, but somehow I still got an alert saying “You got the math wrong!”. If only I could remember which values I had input…

alert("Welcome to Leandro’s age calculation checker");
const leandrosCurrentAge = Number(prompt("Leandro’s current age", "28.8"));
const ageIncrementAfterLecture = Number(
  prompt("Age increment after lecture", "0.1")
);
const expectedAgeAfterLecture = Number(
  prompt("Expected age after lecture", "28.9")
);
const ageAfterLecture = leandrosCurrentAge + ageIncrementAfterLecture;
if (ageAfterLecture === ageAfterLecture) {
  alert("You got the math right!");
} else {
  alert("You got the math wrong!");
}

What’s going on?

More Information

Some ways to produce not-a-number (NaN) in JavaScript:

Number("Not a Number");
0 / 0; // (A nonzero number divided by zero produces ‘Infinity’)
Infinity / Infinity;
NaN + 1; // And other operations with a NaN
"Hi" - 1; // And other operations that don’t make sense, like ‘[] / {}’

The Mystery of That Which Was Itself

For your convenience, I put the code from this mystery in the the-mystery-of-that-which-was-itself branch in TODOOSE.

I was playing with the creation of TODO items in TODOOSE. I wanted to check that it really increased the number of TODO items, so I included some debugging console.log()s in the PlusButton component. Most times it worked as I expected, but sometimes when I clicked on the plus button I got a result that surprised me.

class PlusButton extends React.Component {
  async handleClick() {
    console.log(`Before: ${await this.countItems()}`);
    fetch("/items", { method: "POST" });
    console.log(`After: ${await this.countItems()}`);
  }

  async countItems() {
    return (await (await fetch("/items")).json()).length;
  }

  // ...
}

What’s going on?

More Information

We ran into a problem like this when building TODOOSE.

Announcements

The Overview Presentations are coming up. Follow our recommendations for a good presentation.

Go watch Sandi Metz on this year’s Association for Computing Machinery Lecture Series in Memory of Nathan Krasnopoler!

We’ll also have a session with Sandi just for OOSE students this Tuesday, 2019-10-22, 14:30–15:00, in Malone 338!