Tutorial 2 (week 3)
Tutorial Questions
We will, together, apply Test Driven Development to create a simple Tic-tac-toe game. Meanwhile, think of what tests we should have for the 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 thefunc 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!