Tutorial 2
Tutorial Questions
-
Suppose we are to implement a Tic-Tac-Toe game. We will need an ADT to represent the game state. Suppose we want to use TDD to implement this ADT. Think about the tests we should have for the ADT and game.
-
- What are test doubles?
- How do you use test doubles with dependency injection?
- Test this function, possibly refactoring it to be more testable:
(Assume that the
func sendEmailToUsers(message: String) {
let userEmails = Database.queryAll(table: "users").map { $0.email }
EmailService.sendEmail(to: userEmails, message: message)
}Database.queryAll
andEmailService.sendEmail
methods do exist.) - When is dependency injection useful? When is it not so useful?
-
Refactor the code snippet below to adhere to the SLAP better.
import Foundation
func leftRotate(_ data: UInt32, by amount: Int) -> UInt32 {
return data << amount | data >> (32 - amount)
}
// Taken from https://en.wikipedia.org/wiki/SHA-1#SHA-1_pseudocode
func sha1(_ message: String) -> String {
var data = Data(message.utf8)
var h0: UInt32 = 0x67452301
var h1: UInt32 = 0xEFCDAB89
var h2: UInt32 = 0x98BADCFE
var h3: UInt32 = 0x10325476
var h4: UInt32 = 0xC3D2E1F0
let ml = data.count
data.append(0x80)
data.append(contentsOf: Array(repeating: 0,
count: (64 + (55 - ml) % 64) % 64))
data.append(withUnsafeBytes(of: (ml * 8).bigEndian) { Data($0) })
assert(data.count.isMultiple(of: 64))
for chunkIndex in stride(from: 0, to: data.count, by: 64) {
var words = stride(from: 0, to: 64, by: 4).map { i -> UInt32 in
let word = data[chunkIndex + i..<chunkIndex + i + 4]
return word.withUnsafeBytes { bytes in
bytes.load(as: UInt32.self).bigEndian
}
}
for i in 16..<80 {
words.append(leftRotate(
words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16],
by: 1
))
}
var a = h0
var b = h1
var c = h2
var d = h3
var e = h4
for i in 0..<80 {
var f: UInt32 = 0
var k: UInt32 = 0
switch i {
case 0..<20:
f = (b & c) ^ ((~b) & d)
k = 0x5A827999
case 20..<40:
f = b ^ c ^ d
k = 0x6ED9EBA1
case 40..<60:
f = (b & c) ^ (b & d) ^ (c & d)
k = 0x8F1BBCDC
default:
f = b ^ c ^ d
k = 0xCA62C1D6
}
// &+ is like +, but it does not crash when it overflows
let temp: UInt32 = leftRotate(a, by: 5) &+ f &+ e &+ k &+ words[i]
e = d
d = c
c = leftRotate(b, by: 30)
b = a
a = temp
}
h0 &+= a
h1 &+= b
h2 &+= c
h3 &+= d
h4 &+= e
}
return String(format: "%02x", h0) +
String(format: "%02x", h1) +
String(format: "%02x", h2) +
String(format: "%02x", h3) +
String(format: "%02x", h4)
} -
-
What is the delegate pattern?
-
Suppose we want to build a user interface for the Sudoku puzzle that we have built in Problem Set 0. We will have a
SudokuGridView
UI component (that subclassesUIView
); this component represents the grid that is displayed to the user. The user can then input numbers in this grid to attempt the puzzle.This
SudokuGridView
contains no domain logic. Instead, it will communicate with a delegate class that we can specify with adelegate
property in the class, i.e.class SudokuGridView: UIView {
var delegate: SudokuGridViewDelegate?
}Here,
SudokuGridViewDelegate
is a protocol that acts as a delegate toSudokuGridView
. Suggest methods that should go in this protocol.You may see, e.g.
UITableViewDelegate
for inspiration.
-
-
Watch this video on "Functional Core, Imperative Shell": https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell
(It's only 13 minutes long! It probably can change the way you architect software!)
Comment on the paradigm used by this video. Some guidelines:
- What are the advantages proposed by the "functional core, imperative shell" paradigm?
- In what situations might this paradigm not be applicable?
- The video mentions that only the functional core needs to be tested; the imperative shell needs no tests. What do you think about this?
You might recognise this paradigm from a few patterns, such as:
- the IO monad
- the actor model
- the Flux architecture, used in Redux
If you have time, you might also want to watch this video by the same author: https://www.destroyallsoftware.com/talks/boundaries. It's the same idea described more clearly in 33 minutes, and is also highly recommended!