-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathbenefits-of-throwing-functions-try-swift-underrated-feature.html
343 lines (301 loc) · 20.3 KB
/
benefits-of-throwing-functions-try-swift-underrated-feature.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://use.fontawesome.com/afd448ce82.js"></script>
<!-- Meta Tag -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- SEO -->
<meta name="author" content="Bruno Rocha">
<meta name="keywords" content="Software, Engineering, Blog, Posts, iOS, Xcode, Swift, Articles, Tutorials, OBJ-C, Objective-C, Apple">
<meta name="description" content="I've always found throwing functions to be a very underrated feature in the Swift community. In this article, I've separated some of the benefits of this feature that have drawn my attention the most and shared some of my thoughts on why you should give it a second chance.">
<meta name="title" content="Benefits of using throwing functions (try) - Swift's most underrated feature?">
<meta name="url" content="https://swiftrocks.com/benefits-of-throwing-functions-try-swift-underrated-feature">
<meta name="image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4">
<meta name="copyright" content="Bruno Rocha">
<meta name="robots" content="index,follow">
<meta property="og:title" content="Benefits of using throwing functions (try) - Swift's most underrated feature?"/>
<meta property="og:image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4"/>
<meta property="og:description" content="I've always found throwing functions to be a very underrated feature in the Swift community. In this article, I've separated some of the benefits of this feature that have drawn my attention the most and shared some of my thoughts on why you should give it a second chance."/>
<meta property="og:type" content="website"/>
<meta property="og:url" content="https://swiftrocks.com/benefits-of-throwing-functions-try-swift-underrated-feature"/>
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4"/>
<meta name="twitter:image:alt" content="Page Thumbnail"/>
<meta name="twitter:title" content="Benefits of using throwing functions (try) - Swift's most underrated feature?"/>
<meta name="twitter:description" content="I've always found throwing functions to be a very underrated feature in the Swift community. In this article, I've separated some of the benefits of this feature that have drawn my attention the most and shared some of my thoughts on why you should give it a second chance."/>
<meta name="twitter:site" content="@rockbruno_"/>
<!-- Favicon -->
<link rel="icon" type="image/png" href="images/favicon/iconsmall2.png" sizes="32x32" />
<link rel="apple-touch-icon" href="images/favicon/iconsmall2.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap" rel="stylesheet">
<!-- Bootstrap CSS Plugins -->
<link rel="stylesheet" type="text/css" href="css/bootstrap.css">
<!-- Prism CSS Stylesheet -->
<link rel="stylesheet" type="text/css" href="css/prism4.css">
<!-- Main CSS Stylesheet -->
<link rel="stylesheet" type="text/css" href="css/style48.css">
<link rel="stylesheet" type="text/css" href="css/sponsor4.css">
<!-- HTML5 shiv and Respond.js support IE8 or Older for HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://swiftrocks.com/benefits-of-throwing-functions-try-swift-underrated-feature"
},
"image": [
"https://swiftrocks.com/images/thumbs/thumb.jpg"
],
"datePublished": "2020-06-16T14:00:00+02:00",
"dateModified": "2020-06-16T14:00:00+02:00",
"author": {
"@type": "Person",
"name": "Bruno Rocha"
},
"publisher": {
"@type": "Organization",
"name": "SwiftRocks",
"logo": {
"@type": "ImageObject",
"url": "https://swiftrocks.com/images/thumbs/thumb.jpg"
}
},
"headline": "Benefits of using throwing functions (try) - Swift's most underrated feature?",
"abstract": "I've always found throwing functions to be a very underrated feature in the Swift community. In this article, I've separated some of the benefits of this feature that have drawn my attention the most and shared some of my thoughts on why you should give it a second chance."
}
</script>
</head>
<body>
<div id="main">
<!-- Blog Header -->
<!-- Blog Post (Right Sidebar) Start -->
<div class="container">
<div class="col-xs-12">
<div class="page-body">
<div class="row">
<div><a href="https://swiftrocks.com">
<img id="logo" class="logo" alt="SwiftRocks" src="images/bg/logo2light.png">
</a>
<div class="menu-large">
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-large">
<div class="menu-item">
<a href="blog">blog</a>
</div>
<div class="menu-item">
<a href="about">about</a>
</div>
<div class="menu-item">
<a href="talks">talks</a>
</div>
<div class="menu-item">
<a href="projects">projects</a>
</div>
<div class="menu-item">
<a href="software-engineering-book-recommendations">book recs</a>
</div>
<div class="menu-item">
<a href="games">game recs</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
</div>
<div class="menu-small">
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-small-1">
<div class="menu-item">
<a href="blog">blog</a>
</div>
<div class="menu-item">
<a href="about">about</a>
</div>
<div class="menu-item">
<a href="talks">talks</a>
</div>
<div class="menu-item">
<a href="projects">projects</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-small-2">
<div class="menu-item">
<a href="software-engineering-book-recommendations">book recs</a>
</div>
<div class="menu-item">
<a href="games">game recs</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
</div>
</div>
<div class="content-page" id="WRITEIT_DYNAMIC_CONTENT">
<!--WRITEIT_POST_NAME=Benefits of using throwing functions (try) - Swift's most underrated feature?-->
<!--WRITEIT_POST_HTML_NAME=benefits-of-throwing-functions-try-swift-underrated-feature-->
<!--Add here the additional properties that you want each page to possess.-->
<!--These properties can be used to change content in the template page or in the page itself as shown here.-->
<!--Properties must start with 'WRITEIT_POST'.-->
<!--Writeit provides and injects WRITEIT_POST_NAME and WRITEIT_POST_HTML_NAME by default.-->
<!--WRITEIT_POST_SHORT_DESCRIPTION=I've always found throwing functions to be a very underrated feature in the Swift community. In this article, I've separated some of the benefits of this feature that have drawn my attention the most and shared some of my thoughts on why you should give it a second chance.-->
<!--WRITEIT_POST_SITEMAP_DATE_LAST_MOD=2020-06-16T14:00:00+02:00-->
<!--WRITEIT_POST_SITEMAP_DATE=2020-06-16T14:00:00+02:00-->
<title>Benefits of using throwing functions (try) - Swift's most underrated feature?</title>
<div class="blog-post">
<div class="post-title-index">
<h1>Benefits of using throwing functions (try) - Swift's most underrated feature?</h1>
</div>
<div class="post-info">
<div class="post-info-text">
Published on 16 June 2020
</div>
</div>
<p>I've always found throwing functions (<code>try/catch</code>, or <code>do/catch</code>) to be a very underrated feature in the Swift community. While many native APIs from iOS rely on it, very few people seem to actually use them in their daily projects. Most people seem to either optionally unwrap them (<code>try?</code>) or use other features like the <code>Result</code> type, and in my opinion, this stems from the fact that unless your entire project is built around throwing, the parts that do use it are somewhat annoying to deal with, and don't look very nice code-wise:</p>
<pre><code>func getAValue() -> Int? {
do {
let content = try getAnInteger()
} catch {
return nil
}
}</code></pre>
<div class="sponsor-article-ad-auto hidden"></div>
<p>I've personally avoided this feature a lot for these reasons in favor of things like <b>Promises</b> or the new <code>Result<></code> type, but even then, I still felt that my way of handling errors wasn't good. In fact, I was often falling into the same pitfalls, just in different ways. When I recently refactored one of my open-source CLI tools, I noticed that parts of it would not only look better if they used throwing functions instead, but they also would be considerably easier to unit test. I decided to try it out by refactoring that tool to use this feature, but to avoid falling into that same pit, I made the <b>entire</b> tool rely on it.</p>
<p>The results exceeded all of my expectations. With about ~80% of code coverage, the tool is now very easy to maintain and evolve thanks to the benefits of throwing functions. In this article, I've separated some of the benefits of this feature that have drawn my attention the most and shared some of my thoughts on why you should give it a second chance.</p>
<h2>Throwing functions clean your code by allowing you to focus on what matters</h2>
<p>When you need the value of something wrapped in the <code>Result</code> type, you must switch its result immediately. This will make your method responsible for handling any errors associated with that result:</p>
<pre><code>func getUserProfile() -> Result<Profile, Error> {
let result = database.profileSchema
switch result {
case .success(let profileSchema)
let profile = Profile(profileSchema)
return .success(profile)
case .error(let error)
return .error(error)
}
}</code></pre>
<p>This method now has <b>two responsibilities</b> which must ideally be covered by unit tests, which might not be your intention.</p>
<p>When you use <code>try</code> in a method that is itself <code>throws</code>, you can <b>delegate the treatment of errors to the code that is actually interested in it.</b> This allows you to develop methods that only handle their success cases:</p>
<pre><code>func getUserProfile() throws {
let profileSchema = try database.profileSchema
return Profile(profileSchema)
}</code></pre>
<p>As you can see, I don't need to worry about the database failing to fetch the user's profile schema in this specific method because that's not the point of it -- someone else will handle it if it happens. With these changes, this method is now so short that it possibly doesn't even need to exist anymore -- it could be refactored to a simple `try Profile(database: database)` call (initializers can also be <code>throws</code>!). This benefit is especially visible when your access depends on multiple things that can fail:</p>
<pre><code>func migrateDefaultsToDatabase() throws {
let oldUserProfile = try defaultsWrapper.profileJSON
let converted = try databaseConverter.convertToDatabaseFormat(oldUserProfile)
try database.set(converted, to: .profile)
}</code></pre>
<p>Without throwing functions, you would probably have to divide this operation into multiple methods.</p>
<h2>Throwing functions allows you to better design / unit test fatal problems</h2>
<p>In CLI tools, it's common to halt everything or cause a crash when something goes wrong, like failing to open a file:</p>
<pre><code>func obfuscate(file: File) {
guard let contents = open(file) else {
preconditionFailure()
}
let obfuscatedVersion = obfuscate(string: contents) // can also crash internally!
guard success = save(contents, toFile: file) else {
preconditionFailure()
}
}</code></pre>
<p>Not only this method is impossible to unit test by itself, but other unit tests might also trip these failure conditions and crash your test bundle entirely. While your iOS app probably doesn't crash in these conditions, I have seen my share of similar conditions: using an optional <code>try?</code>, returning things like <code>nil</code> or an empty string, logging the occurrence and having the methods who rely on this information be able to treat these special cases. This can be made better by using <code>Result</code>, but that can make you fall back to the previous issue: your methods now do more than they have to.</p>
<p>Similar to the previous benefit, you can use <code>throws</code> here and defer the actual crash / failure to someone who is actually interested in it. In the case of my CLI tool, all fatal conditions will throw a special <code>FatalError</code>, which only results in a crash if handled by <code>main.swift</code>. In fact, <code>main.swift</code> is the only part of the code that even attempts to handle errors. Everything in the tool is delegate to it, which made the tool's code considerably cleaner.</p>
<pre><code>func obfuscate(file: File) throws {
let contents = try open(file)
let obfuscatedVersion = try obfuscate(string: contents)
try save(contents, toFile: file)
}</code></pre>
<p>You can now unit test that this method succeeds if everything is fine and proceed with your life. It's not necessary to unit test this method's specific failure conditions because the errors are not only <b>not</b> coming from it, it also doesn't handle them -- it just sends them downstream.</p>
<p>For reference, here's an example of a method in my CLI tool that generates a failure condition:</p>
<pre><code>public func deobfuscate(crashFilePath: String, mapPath: String) throws {
let crashFile = File(path: crashFilePath)
let crash = try crashFile.read()
let mapString = try File(path: mapPath).read()
guard let map = ConversionMap(mapString: mapString) else {
throw logger.fatalError(forMessage: "Failed to parse conversion map. Have you passed the correct file?")
}
let result = replace(crashLog: crash, withContentsOfMap: map)
try crashFile.write(contents: result)
}</code></pre>
<p>Custom errors can be made by creating enums that conform to <code>Error</code>. Personally, I like making my custom error inherit from <code>LocalizedError</code> to make <code>error.localizedDescription</code> return a custom description. This can be done by implementing its <code>errorDescription</code> property. (implementing <code>localizedDescription</code> directly doesn't work)</p>
<pre><code>public enum SwiftShieldError: Error, LocalizedError {
case fatal(String)
public var errorDescription: String? {
switch self {
case .fatal(let message):
return message
}
}
}
public func fatalError(forMessage message: String) -> Error {
SwiftShieldError.fatal(message)
}</code></pre>
<h2>XCTestCase has special support for throwing methods</h2>
<p>Perhaps my favorite benefit is that <b>XCTestCase can automatically handle failures in throwing functions.</b> Here's a classic example on how would I unit test something that used <code>Result</code>:</p>
<pre><code>func testSomethingUsingResult() {
let result: Result<String, Error> = getAResult()
guard let string = result.get() else {
XCTFail()
} // You could also use the new XCTUnwrap here.
XCTAssertEqual(string, "aString")
}</code></pre>
<p>Having to bypass error conditions tests is very annoying. Fortunately, XCTestCase allows you to mark any test method as <code>throws</code>, making it automatically fail the test if it throws. By making the example's <code>getAResult()</code> become a throwing <code>getAString()</code> instead, you can refactor this test to a single line and completely ignore the failure conditions.</p>
<pre><code>func testSomethingUsingTryCatch() throws {
XCTAssertEqual(try getAString(), "aString")
}</code></pre>
<p>If you would like to do the reverse, which is testing if something fails, there's no need to switch the result -- you can use the special <code>XCTAssertThrowsError</code> method. You can also use <code>XCTAssertNoThrow</code> to test that something succeeds when the result itself isn't what is being tested.</p>
<p>In general, what I like about this is that I don't need to consider failure cases when the test subject itself isn't the one throwing the errors. If I want to test that this method is working, all I have to do is test its success cases. If anything fails upstream, the test will throw an error and fail. This makes unit testing considerably easier and faster, while still being very durable (if not <b>more</b> durable, in my opinion).</p>
<h2><code>Result</code> can be translated from/to throwing functions</h2>
<p>Although <code>Result</code> is sometimes seen as the opposite to throwing functions, they are actually somewhat interchangeable. It's possible to build <code>Result</code> types from throwing operations and get throwing operations from existing <code>Result</code> instances, which might be helpful if you'd like to play with throwing functions in a project without fully commiting to it.</p>
<pre><code>let result: Result<String, Error> = Result(catching: { try file.read() })
let contents = try result.get()</code></pre>
<h2>Conclusion</h2>
<div class="sponsor-article-ad-auto hidden"></div>
<p>Using throwing functions has been extremely beneficial in my new project, but as I said in the beginning, you might find that most of these benefits only apply if your project is <b>completely</b> using them to handle errors. Still, even if you don't have a full project, you can use <code>Result</code>'s special initializers to treat the gaps and benefit from cleaner methods and more durable unit tests.</p>
<p>The project in question is <a href="https://github.com/rockbruno/swiftshield">SwiftShield</a>. Make sure to check it out (especially the test cases!) to see how these benefits are translated to code.</p>
</div></div>
<div class="blog-post footer-main">
<div class="footer-logos">
<a href="https://swiftrocks.com/rss.xml"><i class="fa fa-rss"></i></a>
<a href="https://twitter.com/rockbruno_"><i class="fa fa-twitter"></i></a>
<a href="https://github.com/rockbruno"><i class="fa fa-github"></i></a>
</div>
<div class="footer-text">
© 2025 Bruno Rocha
</div>
<div class="footer-text">
<p><a href="https://swiftrocks.com">Home</a> / <a href="blog">See all posts</a></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Blog Post (Right Sidebar) End -->
</div>
</div>
</div>
<!-- All Javascript Plugins -->
<script type="text/javascript" src="js/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script type="text/javascript" src="js/prism4.js"></script>
<!-- Main Javascript File -->
<script type="text/javascript" src="js/scripts30.js"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-H8KZTWSQ1R"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-H8KZTWSQ1R');
</script>
</body>
</html>