Let me start with a little background. Laptopmini wanted to know why this code wasn’t compiling. Admittedly, Swift’s error messages leave room for improvement. The tl;dr was: he needed to swap the 2nd and 3rd lines of code. This allows both properties to be initialized before the code refers to self
.
I immediately fixated on those exclamation points. “Are there any circumstances that these URLs will fail during creation? And if so, don’t you want to control that crash?” Laptop pointed out that there was no circumstance where his URLs would be malformed, and it was perfectly safe to use the exclamation point.
Despite this, his approach bugged my aesthetics. There’s no reason those URLs should be created in that initializer. I figured there were better ways to deal with this, moving URL creation out of the initializer and controlling crash landings with better error messages.
First, I considered extending URL
to offer a safer non-optional initializer. The following snippet offers “controlled descent”, providing valuable feedback for any string that cannot be used to construct a URL.
extension URL { /// Non-optional initializer with better fail output public init(safeString string: String) { guard let instance = URL(string: string) else { fatalError("Unconstructable URL: \(string)") } self = instance } }
This seemed too big a hammer for this problem. Instead, I decided to recommend the same approach with a solution localized to the SocketEndPoint
type:
// Returns `URL` with guided fail public enum SocketEndPoint: String { case events = "http://nowhere.com/events" case live = "http://nowhere.com/live" public var url: URL { guard let url = URL(string: self.rawValue) else { fatalError("Unconstructable URL: \(self.rawValue)") } return url } }
This approach allows his init code to simplify. Under the updated design, it uses ordinary URLs in the initializer and no exclamation points.
// His initializer fileprivate init() { self.eventSocket = WebSocket(url: SocketEndPoint.events.url)) self.liveSocket = WebSocket(url: SocketEndPoint.live.url)) self.eventSocket.delegate = self self.liveSocket.delegate = self }
Long story short:
- String enumerations are handy but
SocketEndPoint
wasn’t pulling its weight. Its job is to deliver named URLs. It should do that no matter however the type is implemented. Don’t let types slack off because there’s an “almost there” solution baked into the language. - Keep initializers clean and simple.
- When weighing a universal solution against a simple local one, sometimes it’s best to think small and fix the problem in the immediate context.
- I considered normal URL construction (returning an Optional) and creating a
WebSocket
initializer with implicitly unwrapped optionals (public init(url: URL
!)
) This approach offered no real advantages and lost the ability to report which string was malformed. Also, ugly.
You can see a few of approaches I kicked around at this gist.