Software construction course by MIT

4 minute read

This was a great weekend read. I was familiar with most of the concepts introduced in this course, but it was great to have them introduced in more formal way. The course uses typescript, and I finally got some exposure to it. Maybe as a next step I should use it to build something - something for the future. I think it’s a great course for people just joining the workforce as software devs. This blog post is a very sparse notes of what I found interesting. I left out 99% of the content which I recommend you to read from here. There is a 2019 version which is uses Java that you can find here

Sometimes results are “wrong” but they aren’t captured by static or dynamic checks.

Integer division. 5/2 does not return a fraction, it returns a truncated integer. So this is an example of where what we might have hoped would be a dynamic error (because a fraction isn’t representable as an integer) frequently produces the wrong answer instead.

Integer overflow. The int and long types are actually finite sets of integers, with maximum and minimum values. What happens when you do a computation whose answer is too positive or too negative to fit in that finite range? The computation quietly overflows (wraps around), and returns an integer from somewhere in the legal range but not the right answer.

Floating-point types like double in java have several special values that aren’t real numbers: NaN (which stands for “Not a Number”), POSITIVE_INFINITY, and NEGATIVE_INFINITY. So when you apply certain operations to a double that you’d expect to produce dynamic errors, like dividing by zero or taking the square root of a negative number, you will get one of these special values instead. If you keep computing with it, you’ll end up with a bad final answer.

Testing is Hard

As a programmer you want your test to pass, but as a tester you need to try hard to fail your code.

Exhaustive testing is infeasible. The space of possible test cases is generally too big to cover exhaustively.

Running tests with inputs chosen without any thought (Haphazard testing) is useless - unless the program is so buggy that an arbitrarily-chosen input is more likely to fail than to succeed.

Random or statistical testing doesn’t work well for software. Other engineering disciplines can test small random samples (e.g. 1% of hard drives manufactured) and infer the defect rate for the whole production lot. But it’s not true for software. Software behavior varies discontinuously and discretely across the space of possible inputs. The system may seem to work fine across a broad range of inputs, and then abruptly fail at a single boundary point.

Black Box and Glass Box Testing

Black box testing - The tester has no knowledge of the internal structure or implementation details of the software being tested. The tester treats the system as a “black box” and focuses solely on the inputs and expected outputs.

Glass box testing - The tester has full knowledge of the internal structure, code, and implementation details of the software being tested. So the inputs to the tests can be chosen to trigger different flows that the program is capable of taking.

Code Review

  • DRY principle
  • Avoid magic numbers - use enums/ data structures. Avoid global variables.
  • One purpose for each variable
  • Fail fast - fail as soon as you encounter a bug
  • Actively resist the temptation to handle special cases separately.

Specifications

  • behavioural equivalence - if we can substitute one implementation for another

“Specifications are good for the client of a module because they help make the module easier to understand. Having a specification lets you understand what the module does without having to read the module’s code.” In practice, writing specifications is hard. Assumptions that weren’t explicitly made creep into the code. So spec it needs be re-iterated on everytime you find an assumption that hasn’t been captured by the spec. Good specs are a must for interfaces. This acts as a firewall between client and implementer. Specifications also allow you to auto generate documentation in most languages.

  • Choose empty values over nulls. Avoid nulls in code, both in parameters and output.
  • A good unit test is focused on just a single specification
  • Mutation is disallowed unless stated otherwise. mutating the parameters of a function itself(eg, sorting) is generally discouraged.

How to design a specification

Mutability vs Immutability

“If the effects do not explicitly say that an input can be mutated, then mutation of the input is implicitly disallowed."(Eg, sort)

Even if your implementation isn’t changing a mutable object, some other place might be and lead to bugs in your code.

Most of the time mutable objects are used for performance reasons. Clever implements can take advantage of immutability. Immutability allows you to share many memory locations(as we know they won’t change) thus matching performance requirements. Also, we can hash(and cache) immutable objects.

References for future

  1. Floating point arithmetic is imprecise even for small numbers
  2. What Every Computer Scientist Should Know About Floating-Point Arithmetic
  3. Variable declarations use let and const instead of var
  4. Unit testing in TypeScript
  5. Understanding Immutability and Pure Functions (for OOP)
  6. Debugging Rules