├── .gitattributes ├── .github └── PULL_REQUEST_TEMPLATE.md ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md linguist-documentation=false 2 | *.md linguist-language=Swift 3 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Por favor, quando for submeter alguma PR nova fazer a distinção entre dois tipos de PR (Fix e Sugestão), e colocar qual é o tipo no nome da PR. **Não** mesclar os dois tipos de commits numa mesma PR. 2 | 3 | [FIX] Encontrou algum erro de gramática, acentuação errada, tradução mal feita, imagens ou links quebrados, mande uma deste tipo. 4 | 5 | [SUGESTAO] Encontrou algum exemplo ruim, quer melhorar os parágrafos com mais exemplos, sugerir alguma prática de código limpo que está faltando ou **qualquer mudança que também precise ser feita no repositório original**. 6 | 7 | Essa distinção é importante, pois cada PR do tipo sugestão precisa ser aberta também no repositório original para manter um nível de consistência entre os dois. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Ryan McDermott, Felipe Santos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clean Code Adapted for Swift 2 | 3 | ## Index 4 | 1. [Introduction](#introduction) 5 | 2. [Variables](#variables) 6 | 3. [Functions](#functions) 7 | 4. [Objects and Data Structures](#objects-and-data-structures) 8 | 5. [Classes](#classes) 9 | 6. [SOLID](#solid) 10 | 7. [Testing](#testing) 11 | 8. [Concurrency](#concurrency) 12 | 9. [Error Handling](#error-handling) 13 | 10. [Formatting](#formatting) 14 | 11. [Comments](#comments) 15 | 16 | # Introduction 17 | 18 | ![Humorous image of software quality estimation based on counting how many swear words you shouted while reading the code.](https://github.com/MaatheusGois/clean-code-swift/assets/31082311/33650f3c-7e27-44c1-9dba-4b6b0e359dc3) 19 | 20 | 21 | 22 | Software Engineering principles from Robert C. Martin's book [*Clean Code*](https://github.com/MaatheusGois/clean-code-book/blob/main/The.Robert.C.Martin.Clean.Code.Collection.2011.11.epub), adapted for Swift. This is not a style guide. It is a guide for producing [readable, reusable, and refactorable code](https://github.com/MaatheusGois/3rs-of-software-architecture-for-iOS) in Swift. 23 | 24 | It is not necessary to strictly follow all the principles demonstrated, and even less are they a universal consensus. These principles are guidelines and nothing more, however, they have been codified over many years of collective experience by the authors of *Clean Code*. 25 | 26 | Software engineering is just over 50 years old, and we are still learning a lot. When software architecture is as old as architecture itself, we may have stricter rules to follow. For now, let these guidelines serve as a criterion for evaluating the quality of Swift code that you and your team produce. 27 | 28 | One more thing: learning this will not immediately turn you into a better software developer, and working with these principles for many years does not guarantee that you will not make mistakes. Each portion of code starts as a draft, like wet clay being shaped into its final form. Finally, we carve out imperfections by reviewing with our peers. Don't feel guilty about the first drafts that still need improvement. Instead, focus on refining your code. 29 | 30 | # Variables 31 | 32 | ## Use meaningful and pronounceable variable names 33 | 34 | ### **Bad:** 35 | ```swift 36 | let currentDateStr = DateFormatter.localizedString(from: Date(), dateStyle: .short, timeStyle: .none) 37 | ``` 38 | 39 | **Unclear Variable Name**: The variable name `currentDateStr` suggests it holds a string, which can lead to confusion about its type and usage. 40 | 41 | **Inconsistent Naming**: Using "Str" in the variable name introduces inconsistency with other potential date representations, making the code less readable. 42 | 43 | **Potential for Mistakes**: If the variable is expected to be a date later on, developers may mistakenly use it as a date type, leading to type errors. 44 | 45 | ### **Good:** 46 | ```swift 47 | let currentDate = DateFormatter.localizedString(from: Date(), dateStyle: .short, timeStyle: .none) 48 | ``` 49 | 50 | **Clear Variable Name**: The variable name `currentDate` indicates that it holds a date, making its purpose clear and improving code readability. 51 | 52 | **Type Consistency**: Using a name that reflects the variable's type helps prevent misunderstandings and reduces the chance of errors in type usage. 53 | 54 | **Enhanced Readability**: A straightforward name enhances the overall readability of the code, making it easier for others to understand the intent and function of the variable. 55 | 56 | **Easier Maintenance**: Clear and consistent naming conventions make the codebase easier to maintain and refactor in the future. 57 | 58 | 59 | ## Use the same vocabulary for the same type of variable 60 | 61 | ### **Bad:** 62 | ```swift 63 | func getUserInfo() 64 | func getClientData() 65 | func getCustomerRecord() 66 | ``` 67 | 68 | ### **Good:** 69 | ```swift 70 | func getUser() 71 | ``` 72 | 73 | 74 | ## Use searchable names 75 | 76 | We will read more code than we write. It is important that the code we write is readable and searchable. *Not* giving meaningful names to variables that are significant for understanding our program hurts our readers. Make your names searchable. 77 | 78 | ### **Bad:** 79 | ```swift 80 | // What the heck does 86400000 stand for? 81 | setTimeout(blastOff, 86400000) 82 | ``` 83 | 84 | **Unclear Magic Number**: The use of `86400000` directly in the code makes it unclear what this value represents, leading to confusion for anyone reading the code. 85 | 86 | **Lack of Context**: Without a descriptive name, the purpose of the number is ambiguous, making the code harder to understand and maintain. 87 | 88 | **Reduced Readability**: Using magic numbers reduces overall code readability, as developers cannot easily grasp the meaning behind the value. 89 | 90 | ### **Good:** 91 | ```swift 92 | // Declare them as `let` or `var` in uppercase letters. 93 | let millisecondsPerDay = 86400000 94 | 95 | setTimeout(blastOff, millisecondsPerDay) 96 | ``` 97 | 98 | **Descriptive Variable Name**: Defining `millisecondsPerDay` provides clear context, making the purpose of the value immediately understandable. 99 | 100 | **Enhanced Clarity**: By using a named constant, the code becomes clearer, improving readability and comprehension for other developers. 101 | 102 | **Easier Maintenance**: With a named constant, changes to the value can be made in one place, simplifying future updates and reducing the risk of errors. 103 | 104 | **Consistent Coding Practices**: Using `let` or `var` in uppercase letters for constants promotes consistency and helps convey intent within the code. 105 | 106 | ## Use explanatory variables 107 | 108 | ### **Bad:** 109 | 110 | ```swift 111 | let address = "One Infinite Loop, Cupertino 95014" 112 | let cityZipCodeRegex = try! NSRegularExpression( 113 | pattern: "^[^,\\]+[,\\\\\\s]+(.+?)\\s*(\\d{5})?$" 114 | ) 115 | 116 | saveCityZipCode(address.match(cityZipCodeRegex)![1], address.match(cityZipCodeRegex)![2]) 117 | ``` 118 | 119 | **Unclear Variable Usage**: The use of `address.match(cityZipCodeRegex)!` without explanation makes it unclear what is being extracted. 120 | 121 | **Risk of Runtime Errors**: Force unwrapping can lead to crashes if no matches are found, reducing the robustness of the code. 122 | 123 | **Reduced Readability**: The code lacks clarity about what is being processed and stored, making it harder to understand. 124 | 125 | ### **Good:** 126 | 127 | ```swift 128 | let address = "One Infinite Loop, Cupertino 95014" 129 | let cityZipCodeRegex = try! NSRegularExpression( 130 | pattern: "^[^,\\]+[,\\\\\\s]+(.+?)\\s*(\\d{5})?$" 131 | ) 132 | 133 | if let match = cityZipCodeRegex.firstMatch(in: address) { 134 | let city = (address as NSString).substring( 135 | with: match.range(at: 1) 136 | ) 137 | let zipCode = (address as NSString).substring( 138 | with: match.range(at: 2) 139 | ) 140 | 141 | saveCityZipCode(city, zipCode) 142 | } 143 | ``` 144 | 145 | 146 | **Descriptive Variable Names**: Extracting city and zip code into clearly named variables enhances the readability and intent of the code. 147 | 148 | **Safe Handling of Matches**: Using optional binding prevents potential crashes, improving the stability of the code. 149 | 150 | **Improved Clarity**: The structure of the code makes it clear what each part is doing, facilitating easier maintenance and collaboration. 151 | 152 | ## Avoid Mental Mapping 153 | 154 | Explicit is better than implicit. Using clear and descriptive variable names helps prevent confusion and enhances code readability. 155 | 156 | ### **Bad:** 157 | 158 | ```swift 159 | let locations = ["Austin", "New York", "San Francisco"] 160 | locations.forEach({ l in 161 | doStuff() 162 | doSomeOtherStuff() 163 | // ... 164 | // ... 165 | // ... 166 | // Wait, what's `l` for again? 167 | dispatch(l) 168 | }) 169 | ``` 170 | 171 | **Ambiguous Variable Name**: The variable `l` is not descriptive, making it unclear what it represents within the context. 172 | 173 | **Cognitive Load**: Readers must mentally map the variable to its intended purpose, leading to confusion and potential errors. 174 | 175 | **Reduced Readability**: The code's readability suffers, making it harder for others (or even the original author) to understand the logic quickly. 176 | 177 | ### **Good:** 178 | 179 | ```swift 180 | let locations = ["Austin", "New York", "San Francisco"] 181 | locations.forEach({ location in 182 | doStuff() 183 | doSomeOtherStuff() 184 | // ... 185 | // ... 186 | // ... 187 | dispatch(location) 188 | }) 189 | ``` 190 | 191 | **Descriptive Variable Name**: Using `location` clarifies the purpose of the variable, enhancing code clarity. 192 | 193 | **Lower Cognitive Load**: Clear naming reduces the need for mental mapping, making the code easier to follow. 194 | 195 | **Improved Readability**: The explicit nature of the code enhances overall readability, facilitating collaboration and maintenance. 196 | 197 | ## Avoid Unnecessary Context 198 | 199 | Using descriptive names is important, but avoid repeating the object or class name in variable names to keep your code concise and readable. 200 | 201 | ### **Bad:** 202 | ```swift 203 | class User { 204 | var userName: String 205 | var userAge: Int 206 | 207 | init(userName: String, userAge: Int) { 208 | self.userName = userName 209 | self.userAge = userAge 210 | } 211 | } 212 | ``` 213 | 214 | **Redundant Naming**: The prefixes `userName` and `userAge` add unnecessary context, making the variable names longer without enhancing clarity. 215 | 216 | **Decreased Readability**: The repetition of the class name in variable names can clutter the code, making it harder to read. 217 | 218 | **Maintenance Challenges**: Changes to the class name would require updating multiple variable names, increasing the risk of inconsistencies. 219 | 220 | ### **Good:** 221 | 222 | ```swift 223 | class User { 224 | var name: String 225 | var age: Int 226 | 227 | init(name: String, age: Int) { 228 | self.name = name 229 | self.age = age 230 | } 231 | } 232 | ``` 233 | 234 | **Concise Variable Names**: Using `name` and `age` eliminates unnecessary context while maintaining clarity about each variable's purpose. 235 | 236 | **Enhanced Readability**: The code is cleaner and easier to understand, allowing developers to grasp the logic quickly. 237 | 238 | **Simplified Maintenance**: With less redundancy, there’s a lower risk of errors during updates, making the codebase more maintainable. 239 | 240 | # Functions 241 | 242 | ## Function Arguments (Max 2 params) 243 | 244 | Careful management of parameters in functions is essential for facilitating testing and maintaining code while adhering to SOLID principles. When a function has more than three arguments, complexity increases significantly, requiring detailed tests for each parameter. It is best to limit function arguments to a maximum of two, and avoid three whenever possible. If more parameters are needed, consider consolidating them into an object. Functions with too many arguments often indicate multiple responsibilities, suggesting a need for reassessment. In many cases, using an object as an argument is a practical solution, especially given Swift's efficiency in creating objects. 245 | 246 | ### **Bad:** 247 | ```swift 248 | func createMenu(title: String, body: String, buttonText: String, cancellable: Bool) { 249 | // ... 250 | } 251 | ``` 252 | 253 | **Excessive Arguments**: The function has four parameters, which complicates the function signature and increases the cognitive load for users. 254 | 255 | **Increased Complexity**: More parameters lead to a higher chance of errors and make testing more difficult, as each parameter needs separate consideration. 256 | 257 | **Violation of Single Responsibility**: This function may be trying to handle multiple responsibilities, indicating a design issue that needs to be addressed. 258 | 259 | ### **Good:** 260 | ```swift 261 | struct MenuViewData { 262 | let title: String 263 | let body: String 264 | let buttonText: String 265 | let cancellable: Bool 266 | } 267 | 268 | func createMenu(viewData: MenuViewData) { 269 | // ... 270 | } 271 | 272 | // Usage: 273 | let viewData = MenuViewData( 274 | title: "Foo", 275 | body: "Bar", 276 | buttonText: "Baz", 277 | cancellable: true 278 | ) 279 | 280 | createMenu(viewData: viewData) 281 | ``` 282 | 283 | **Consolidated Parameters**: Using a `MenuViewData` struct consolidates multiple parameters into a single object, simplifying the function signature. 284 | 285 | **Reduced Complexity**: The function is now easier to use and understand, reducing the cognitive load for developers. 286 | 287 | **Adherence to Single Responsibility**: By consolidating parameters, the function focuses on a single responsibility, aligning with best practices and SOLID principles. 288 | 289 | ## **Use Default Arguments Instead of Short-Circuiting or Using Conditionals** 290 | 291 | Default arguments provide cleaner and more readable code compared to short-circuits. Note that default values are only applied to `nullable` arguments; other "falsy" values like `""`, `false`, `null`, and `0` will not trigger default values. 292 | 293 | ### **Bad:** 294 | ```swift 295 | func createMicrobrewery(name: String?) { 296 | let breweryName = name ?? "Hipster Brew Co." 297 | // ... 298 | } 299 | ``` 300 | 301 | **Redundant Conditional Logic**: The use of `??` introduces unnecessary complexity when setting a default value for `name`. 302 | 303 | **Decreased Readability**: The reliance on conditionals can make the code harder to follow, especially for developers unfamiliar with the logic. 304 | 305 | **Potential for Misuse**: If a falsy value is passed, the function will still set `breweryName` to the provided value instead of the default. 306 | 307 | ### **Good:** 308 | ```swift 309 | func createMicrobrewery(breweryName: String = "Hipster Brew Co.") { 310 | // ... 311 | } 312 | ``` 313 | 314 | **Clean Default Argument**: Using a default argument simplifies the function signature and eliminates the need for additional conditional logic. 315 | 316 | **Enhanced Readability**: The code is more straightforward, making it easier for developers to understand the intended behavior of the function. 317 | 318 | **Clear Intent**: Default arguments clearly communicate the function's design, ensuring that a sensible default is always available without complicating the implementation. 319 | 320 | ## Functions Should Do One Thing 321 | 322 | This is one of the most important principles in software engineering. Functions that perform multiple tasks become difficult to compose, test, and reason about. By isolating a function to perform just one action, you can refactor more easily, leading to cleaner code. If you remember nothing else from this guide, this principle will put you ahead of many developers. 323 | 324 | ### **Bad:** 325 | ```swift 326 | func emailClients(clients: [Client]) { 327 | clients.forEach { client in 328 | let clientRecord = database.lookup(client) 329 | if clientRecord.isActive() { 330 | email(client) 331 | } 332 | } 333 | } 334 | ``` 335 | 336 | **Multiple Responsibilities**: This function handles both looking up client records and sending emails, violating the single responsibility principle. 337 | 338 | **Decreased Testability**: Testing becomes more complex because the function combines logic that could be isolated into separate tests. 339 | 340 | **Reduced Readability**: The function's intent is muddied by combining tasks, making it harder to understand at a glance. 341 | 342 | ### **Good:** 343 | ```swift 344 | func emailActiveClients(clients: [Client]) { 345 | clients 346 | .filter { isActiveClient(client: $0) } 347 | .forEach { email(client: $0) } 348 | } 349 | 350 | func isActiveClient(client: Client) -> Bool { 351 | let clientRecord = database.lookup(client) 352 | return clientRecord.isActive() 353 | } 354 | ``` 355 | 356 | **Single Responsibility**: Each function has a clear, distinct purpose—`emailActiveClients` handles emailing, while `isActiveClient` checks client status. 357 | 358 | **Improved Testability**: Smaller, focused functions are easier to test individually, facilitating more effective unit tests. 359 | 360 | **Enhanced Readability**: The code is clearer and more straightforward, allowing developers to understand the logic and flow at a glance. 361 | 362 | 363 | ## Function Names Should Say What They Do 364 | 365 | Clear and descriptive function names enhance code readability and maintainability. Function names should provide insight into their purpose, making it easier for developers to understand the code at a glance. 366 | 367 | ### **Bad:** 368 | ```swift 369 | func addToDate(date: Date, month: Int) { 370 | // ... 371 | } 372 | 373 | let date = Date() 374 | 375 | // It's hard to tell by the function name what is being added 376 | addToDate(date: date, month: 1) 377 | ``` 378 | 379 | **Ambiguous Naming**: The function name `addToDate` does not specify what is being added, leading to confusion about its purpose. 380 | 381 | **Decreased Clarity**: Without a clear indication of functionality, developers must delve into the implementation to understand what the function does, hindering readability. 382 | 383 | **Potential for Misuse**: The vague name increases the risk of misuse, as developers may not fully grasp the function's intent. 384 | 385 | ### **Good:** 386 | ```swift 387 | func addMonthToDate(month: Int, date: Date) { 388 | // ... 389 | } 390 | 391 | let date = Date() 392 | addMonthToDate(month: 1, date: date) 393 | ``` 394 | 395 | **Descriptive Naming**: The function name `addMonthToDate` clearly indicates that a month is being added to a date, enhancing understanding. 396 | 397 | **Improved Clarity**: A straightforward name allows developers to grasp the function's purpose immediately, increasing overall code readability. 398 | 399 | **Reduced Risk of Misuse**: Clear naming minimizes the chance of misuse by conveying the function's intent explicitly. 400 | 401 | 402 | ## Functions Should Have One Level of Abstraction 403 | 404 | When functions operate on more than one level of abstraction, they tend to become overloaded with responsibilities. Splitting functions into single-level abstractions leads to better reusability and testability. 405 | 406 | ### **Bad:** 407 | 408 | ```swift 409 | func parseInput(input: String) { 410 | // ... 411 | let inputData = ... 412 | // ... 413 | saveData(inputData) 414 | } 415 | 416 | func saveData(data: Data) { 417 | // ... 418 | database.save(data) 419 | } 420 | ``` 421 | 422 | **Multiple Levels of Abstraction**: The `parseInput` function handles both parsing and saving, leading to a lack of clarity in its responsibilities. 423 | 424 | **Reduced Reusability**: Mixing different levels of logic in a single function makes it harder to reuse components in different contexts. 425 | 426 | **Complicated Testing**: Functions that combine multiple abstractions are more challenging to test effectively due to their interdependent logic. 427 | 428 | ### **Good:** 429 | 430 | ```swift 431 | func parseInput(input: String) { 432 | // ... 433 | let inputData = ... 434 | saveData(data: inputData) 435 | } 436 | 437 | func saveData(data: Data) { 438 | // ... 439 | database.save(data) 440 | } 441 | ``` 442 | 443 | **Single Level of Abstraction**: Each function focuses on one specific task, improving clarity regarding what each function is responsible for. 444 | 445 | **Enhanced Reusability**: With clear separation, both `parseInput` and `saveData` can be reused in other contexts without complications. 446 | 447 | **Simplified Testing**: Isolated functions are easier to test individually, allowing for more straightforward unit tests and better code quality. 448 | 449 | ## Remove Duplicated Code 450 | 451 | Do your best to avoid duplicated code. Duplicated code is bad because it means there's more than one place to change something if you need to make a change. 452 | 453 | Imagine if you run a restaurant and you keep a list of all your clients in two places: one where you keep the order for the chef and another where you keep the order for the delivery. If you have clients that cancel, now you have to cancel in two places. If you only have one list, there's only one place to update! 454 | 455 | What if you forget to update in one place and not the other? What if the delivery guy shows up while the chef is making something and he hasn't seen the cancellation? Now you have a problem. 456 | 457 | Often, you have duplicated code because you have two or more slightly different things, that share a lot in common but their differences force you to have two or more separate methods that do much of the same things. Removing duplicated code means creating an abstraction that can handle those differences with only one function/method. 458 | 459 | By doing this, you now have only one place to update if something changes. 460 | 461 | ### **Bad:** 462 | ```swift 463 | func showDeveloper(name: String) { 464 | print("Developer: \(name)") 465 | print("Coding...") 466 | } 467 | 468 | func showManager(name: String) { 469 | print("Manager: \(name)") 470 | print("Meeting...") 471 | } 472 | ``` 473 | 474 | **Code Duplication**: The functions `showDeveloper` and `showManager` have similar structures and repeated logic, making them harder to maintain. 475 | 476 | **Increased Maintenance Overhead**: Changes to the output format or logic would need to be implemented in multiple places, increasing the chance of errors. 477 | 478 | **Potential for Inconsistencies**: If updates are made to one function and not the other, inconsistencies can arise in behavior or output. 479 | 480 | ### **Good:** 481 | ```swift 482 | enum Role { 483 | case developer 484 | case manager 485 | 486 | var description: String { 487 | switch self { 488 | case .developer: return "Coding..." 489 | case .manager: return "Meeting..." 490 | } 491 | } 492 | } 493 | 494 | func showPerson(name: String, role: Role) { 495 | print("\(role): \(name)") 496 | print(role.description) 497 | } 498 | ``` 499 | 500 | **Eliminated Duplication**: The `showPerson` function abstracts the common logic while using the `Role` enum to manage specific behavior, reducing code redundancy. 501 | 502 | **Simplified Maintenance**: With only one function to update, any changes to the logic or output format can be made in a single location. 503 | 504 | **Improved Clarity**: The use of the `Role` enum clearly defines the roles and their specific behaviors, making the code easier to understand and manage. 505 | 506 | ## Avoid Using Flags as Function Parameters 507 | Functions that have boolean flags as parameters are harder to understand than functions that do only one thing. Flags indicate that the function does more than one thing. Separate these functions into multiple functions if necessary. 508 | 509 | ### **Bad:** 510 | ```swift 511 | func createFile(name: String, temporary: Bool) { 512 | if temporary { 513 | // Creates a temporary file 514 | } else { 515 | // Creates a permanent file 516 | } 517 | } 518 | ``` 519 | 520 | ### **Good:** 521 | ```swift 522 | func createTemporaryFile(name: String) { 523 | // Creates a temporary file 524 | } 525 | 526 | func createPermanentFile(name: String) { 527 | // Creates a permanent file 528 | } 529 | ``` 530 | 531 | 532 | ## Avoid Side Effects (Part 1) 533 | A function produces a side effect if it does something other than taking an input value and returning another value(s). A side effect can be writing to a file, modifying a global variable, or accidentally transferring all your money to a stranger. 534 | 535 | Now, you need side effects occasionally in your program. Like in the previous example, you might need to write to a file. What you want to do is to centralize where you are doing this. Don't have multiple functions and classes that write to a particular file. Have one service that does it. One and only one. 536 | 537 | The main point is to avoid pitfalls like sharing state between objects with no structure, using mutable data types that can be written to by anything, and not centralizing where your side effects occur. If you can do this, you will be much happier than the vast majority of other programmers. 538 | 539 | ### **Bad:** 540 | ```swift 541 | // Global variable referenced by the following function 542 | // If we had another function that uses this name, then it would be an array and could break your code 543 | var name = "Matheus Gois" 544 | 545 | func splitIntoFirstAndLastName() { 546 | name = name.split(separator: " ").joined(separator: " ") 547 | } 548 | 549 | splitIntoFirstAndLastName() 550 | 551 | print(name) // 'Matheus Gois' 552 | ``` 553 | 554 | ### **Good:** 555 | ```swift 556 | func splitIntoFirstAndLastName(name: String) -> (firstName: String, lastName: String) { 557 | let components = name.split(separator: " ").map { String($0) } 558 | guard components.count >= 2 else { 559 | return (firstName: name, lastName: "") 560 | } 561 | let firstName = components[0] 562 | let lastName = components[1.. [CartItem] { 602 | var updatedCart = cart 603 | updatedCart.append(item) 604 | return updatedCart 605 | } 606 | ``` 607 | 608 | ## Do Not Write to Global Functions 609 | Polluting globals is a bad practice in Swift because you can conflict with another library, and the user of your API would have no idea until they got an exception thrown in production. Let's think about an example: what if you wanted to extend the native Swift Array method to have a `diff` method that could show the difference between two arrays? You could write your new function on `Array.prototype`, but it could collide with another library that tried to do the same thing. And what if this other library was just using `diff` to find the difference between the first and last element of an array? 610 | 611 | ### **Bad:** 612 | ```swift 613 | extension Array { 614 | func diff(_ comparisonArray: [Element]) -> [Element] { 615 | let hash = Set(comparisonArray) 616 | return filter { !hash.contains($0) } 617 | } 618 | } 619 | ``` 620 | 621 | ### **Good:** 622 | ```swift 623 | class ExtendedArray: Array { 624 | func diff(_ comparisonArray: [Element]) -> [Element] { 625 | let hash = Set(comparisonArray) 626 | return filter { !hash.contains($0) } 627 | } 628 | } 629 | ``` 630 | 631 | 632 | ## Favor Functional Programming over Imperative Programming 633 | Swift is not a functional language in the same way Haskell is, but it has a touch of functional in it. Functional languages are cleaner and easier to test. Favor this type of programming when you can. 634 | 635 | ### **Bad:** 636 | ```swift 637 | let programmerOutput = [ 638 | Programmer(name: "Uncle Bobby", linesOfCode: 500), 639 | Programmer(name: "Suzie Q", linesOfCode: 1500), 640 | Programmer(name: "Jimmy Gosling", linesOfCode: 150), 641 | Programmer(name: "Gracie Hopper", linesOfCode: 1000) 642 | ] 643 | 644 | var totalOutput = 0 645 | 646 | for programmer in programmerOutput { 647 | totalOutput += programmer.linesOfCode 648 | } 649 | ``` 650 | 651 | ### **Good:** 652 | ```swift 653 | let programmerOutput 654 | 655 | = [ 656 | Programmer(name: "Uncle Bobby", linesOfCode: 500), 657 | Programmer(name: "Suzie Q", linesOfCode: 1500), 658 | Programmer(name: "Jimmy Gosling", linesOfCode: 150), 659 | Programmer(name: "Gracie Hopper", linesOfCode: 1000) 660 | ] 661 | 662 | let totalOutput = programmerOutput 663 | .map { $0.linesOfCode } 664 | .reduce(0, +) 665 | ``` 666 | 667 | 668 | ## Encapsulate Conditionals 669 | 670 | ### **Bad:** 671 | ```swift 672 | if fsm.state == "fetching" && isEmpty(listNode) { 673 | // ... 674 | } 675 | ``` 676 | 677 | ### **Good:** 678 | ```swift 679 | func shouldShowSpinner(fsm: FSM, listNode: Node) -> Bool { 680 | return fsm.state == "fetching" && isEmpty(listNode) 681 | } 682 | 683 | if shouldShowSpinner(fsm: fsmInstance, listNode: listNodeInstance) { 684 | // ... 685 | } 686 | ``` 687 | 688 | 689 | ## Avoid Negations in Conditionals 690 | 691 | ### **Bad:** 692 | ```swift 693 | func isViewNotPresent(view: UIView) -> Bool { 694 | // ... 695 | } 696 | 697 | if !isViewNotPresent(view: view) { 698 | // ... 699 | } 700 | ``` 701 | 702 | ### **Good:** 703 | ```swift 704 | func isViewPresent(view: UIView) -> Bool { 705 | // ... 706 | } 707 | 708 | if isViewPresent(view: view) { 709 | // ... 710 | } 711 | ``` 712 | 713 | 714 | ## Avoid Conditionals 715 | This seems like an impossible task. The first time people hear this, they say, "How am I supposed to do anything without using `if`?" The answer is that you can use polymorphism to achieve the same task in different cases. The second question is usually, "Well, that's great, but why would I do that?" The answer is a previously learned clean code concept: a function should do only one thing. When you have classes and functions that have `if` statements, you're telling your user that your function does more than one thing. Remember, do one thing. 716 | 717 | ### **Bad:** 718 | ```swift 719 | class Airplane { 720 | // ... 721 | func getCruisingAltitude() -> Int { 722 | switch self.type { 723 | case "777": 724 | return self.getMaxAltitude() - self.getPassengerCount() 725 | case "Air Force One": 726 | return self.getMaxAltitude() 727 | case "Cessna": 728 | return self.getMaxAltitude() - self.getFuelExpenditure() 729 | default: 730 | return 0 731 | } 732 | } 733 | } 734 | ``` 735 | 736 | ### **Good:** 737 | ```swift 738 | class Airplane { 739 | // ... 740 | } 741 | 742 | class Boeing777: Airplane { 743 | // ... 744 | func getCruisingAltitude() -> Int { 745 | return self.getMaxAltitude() - self.getPassengerCount() 746 | } 747 | } 748 | 749 | class AirForceOne: Airplane { 750 | // ... 751 | func getCruisingAltitude() -> Int { 752 | return self.getMaxAltitude() 753 | } 754 | } 755 | 756 | class Cessna: Airplane { 757 | // ... 758 | func getCruisingAltitude() -> Int { 759 | return self.getMaxAltitude() - self.getFuelExpenditure() 760 | } 761 | } 762 | ``` 763 | 764 | 765 | ## Avoid Type Checking (Part 1) 766 | Swift does not have types, which means your functions can take any type of argument. Sometimes this freedom can bite you, and it becomes tempting to do type checking in your functions. There are many ways to avoid having to do this. The first thing to consider is consistent APIs. 767 | 768 | ### **Bad:** 769 | ```swift 770 | func travelToTexas(vehicle: Any) { 771 | if let bicycle = vehicle as? Bicycle { 772 | bicycle.pedal(currentLocation: self.currentLocation, newLocation: Location("texas")) 773 | } else if let car = vehicle as? Car { 774 | car.drive(currentLocation: self.currentLocation, newLocation: Location("texas")) 775 | } 776 | } 777 | ``` 778 | 779 | ### **Good:** 780 | ```swift 781 | func travelToTexas(vehicle: Vehicle) { 782 | vehicle.move(currentLocation: self.currentLocation, newLocation: Location("texas")) 783 | } 784 | ``` 785 | 786 | 787 | ## Avoid Type Checking (Part 2) 788 | If you are working with basic primitive values like strings and integers, and you cannot use polymorphism, but still feel the need to check the type, you should consider using TypeScript. It is an excellent alternative to regular Swift, as it provides static typing on top of Swift's standard syntax. The problem with manual checking in Swift is that to do it well requires so much extra verbosity that the false "type safety" you get doesn't compensate for the loss of readability. Keep your Swift clean, write good tests, and have good code reviews. Or alternatively, do all of that but with TypeScript (which, as I mentioned, is a great alternative!). 789 | 790 | ### **Bad:** 791 | ```swift 792 | func combine(val1: Any, val2: Any) -> String { 793 | if let number1 = val1 as? Int, let number2 = val2 as? Int { 794 | return String(number1 + number2) 795 | } else if let string1 = val1 as? String, let string2 = val2 as? String { 796 | return string1 + string2 797 | } 798 | 799 | fatalError("Must be of type String or Number") 800 | } 801 | ``` 802 | 803 | ### **Good:** 804 | ```swift 805 | func combine(val1: Any, val2: Any) -> String { 806 | return String(describing: val1) + String(describing: val2) 807 | } 808 | ``` 809 | 810 | 811 | ## Remove Dead Code 812 | Dead code is as bad as duplicate code. There is no reason to keep it in your codebase. If it's not being called, get rid of it. It will still be safe in your version history if you ever need it. 813 | 814 | ### **Bad:** 815 | ```swift 816 | func oldRequestModule(url: String) { 817 | // ... 818 | } 819 | 820 | func newRequestModule(url: String) { 821 | // ... 822 | } 823 | 824 | let req = newRequestModule 825 | inventoryTracker(item: "apples", requestModule: req, url: "www.inventory-awesome.io") 826 | ``` 827 | 828 | ### **Good:** 829 | ```swift 830 | func newRequestModule(url: String) { 831 | // ... 832 | } 833 | 834 | let req = newRequestModule 835 | inventoryTracker(item: "apples", requestModule: req, url: "www.inventory-awesome.io") 836 | ``` 837 | 838 | 839 | # Objects and Data Structures 840 | 841 | ## Use Pure Objects 842 | 843 | Objects are said to be pure when they don't share state with other objects. Imagine you're in outer space and you have a spaceship. This spaceship has a fuel tank. Imagine there are various different systems in the spaceship that can modify this fuel tank. 844 | 845 | There are three different types of objects here: 846 | 847 | 1. **Impure Object:** 848 | ```swift 849 | class Spaceship { 850 | var fuelTank: Int 851 | 852 | init(fuelTank: Int) { 853 | self.fuelTank = fuelTank 854 | } 855 | 856 | func launch() { 857 | Rocket().ignite(boosters: self.fuelTank) 858 | } 859 | 860 | func addFuel(fuel: Int) { 861 | self.fuelTank += fuel 862 | } 863 | } 864 | ``` 865 | 866 | 2. **Less Pure Object:** 867 | ```swift 868 | class Spaceship { 869 | var fuelTank: Int 870 | 871 | init(fuelTank: Int) { 872 | self.fuelTank = fuelTank 873 | } 874 | 875 | func launch() { 876 | Rocket().ignite(boosters: self.fuelTank) 877 | } 878 | 879 | func visitSpaceStation(spaceStation: SpaceStation) { 880 | spaceStation.refuel(ship: self) 881 | } 882 | } 883 | 884 | class SpaceStation { 885 | func refuel(ship: Spaceship) { 886 | ship.addFuel(fuel: self.fuelTank) 887 | } 888 | 889 | var fuelTank: Int 890 | } 891 | ``` 892 | 893 | 3. **Pure Object:** 894 | ```swift 895 | class Spaceship { 896 | var fuelTank: Int 897 | 898 | init(fuelTank: Int) { 899 | self.fuelTank = fuelTank 900 | } 901 | 902 | func launch() { 903 | Rocket().ignite(boosters: self.fuelTank) 904 | } 905 | 906 | func refuel(amount: Int) -> Spaceship { 907 | return Spaceship(fuelTank: self.fuelTank + amount) 908 | } 909 | } 910 | ``` 911 | 912 | Why are pure objects preferable? They are easier to test and understand. They cannot be changed by other systems while they are being used. Data passed to them can be trusted, and they have no side effects that can cause difficult-to-trace bugs. 913 | 914 | ## Make decisions based on an object 915 | 916 | ### **Bad:** 917 | ```swift 918 | if car.engine.type == "v8" { 919 | // ... 920 | } 921 | 922 | if bike.tires.type == "fat" { 923 | // ... 924 | 925 | 926 | } 927 | ``` 928 | 929 | ### **Good:** 930 | ```swift 931 | enum Engine { 932 | case v8 933 | } 934 | 935 | enum Tire { 936 | case fat 937 | } 938 | 939 | if car.engine == .v8 { 940 | // ... 941 | } 942 | 943 | if bike.tires == .fat { 944 | // ... 945 | } 946 | ``` 947 | 948 | ## Use Getters and Setters 949 | Using getters and setters to access data in objects is much better than simply looking for a property on an object. "Why?" you might ask. Well, here's an unordered list of reasons: 950 | 951 | * When you want to do more than get a property of an object, you don't have to search and change all the accessors in your code. 952 | * Makes it easier to do validation when setting. 953 | * Encapsulates the internal representation. 954 | * Easier to add logging and error handling when getting and setting. 955 | * You can use lazy loading on your object's properties, for example, fetching it from a server. 956 | 957 | ### **Bad:** 958 | ```swift 959 | class User { 960 | var name: String 961 | var age: Int 962 | 963 | init(name: String, age: Int) { 964 | self.name = name 965 | self.age = age 966 | } 967 | } 968 | 969 | // Usage 970 | let user = User(name: "John", age: 30) 971 | print(user.name) // John 972 | user.name = "" // No validation, name can be empty 973 | user.age = -5 // No validation, age can be negative 974 | print(user.age) // -5 975 | ``` 976 | 977 | 978 | **Lack of Validation**: Properties are set directly without any validation, allowing invalid values (e.g., empty name or negative age). 979 | 980 | **Uncontrolled Access**: Public properties can be modified freely, leading to potential inconsistencies and errors. 981 | 982 | **No Encapsulation**: Internal data is exposed directly, violating the principle of encapsulation and making the class harder to maintain. 983 | 984 | **Inconsistent State Management**: Without validation, the object's state can become inconsistent, leading to potential bugs and unexpected behavior. 985 | 986 | **Difficult to Add Logic**: Adding additional logic, such as logging or lazy loading, becomes more difficult without a controlled access mechanism. 987 | 988 | ### **Good:** 989 | ```swift 990 | class User { 991 | private var _name: String 992 | private var _age: Int 993 | 994 | init(name: String, age: Int) { 995 | self._name = name 996 | self._age = age 997 | } 998 | 999 | var name: String { 1000 | get { 1001 | return _name 1002 | } 1003 | set { 1004 | if newValue.isEmpty { 1005 | print("Name cannot be empty.") 1006 | } else { 1007 | _name = newValue 1008 | } 1009 | } 1010 | } 1011 | 1012 | var age: Int { 1013 | get { 1014 | return _age 1015 | } 1016 | set { 1017 | if newValue < 0 { 1018 | print("Age cannot be negative.") 1019 | } else { 1020 | _age = newValue 1021 | } 1022 | } 1023 | } 1024 | } 1025 | 1026 | // Usage 1027 | let user = User(name: "John", age: 30) 1028 | print(user.name) // John 1029 | user.name = "" // No validation, name can be empty 1030 | user.age = -5 // No validation, age can be negative 1031 | user.age = 25 1032 | print(user.age) // 25 1033 | ``` 1034 | 1035 | 1036 | **Error Handling**: Getters and setters allow for validation and error handling, ensuring that only valid values are set. 1037 | 1038 | **Encapsulation**: Private variables and controlled access through getters and setters protect the internal state and enforce encapsulation. 1039 | 1040 | **Maintainability**: Encapsulated access makes the code easier to maintain and extend, allowing for additional logic (e.g., logging, validation) to be added in one place. 1041 | 1042 | **Consistency**: Consistent access patterns ensure that data modifications go through the same validation process, preventing inconsistencies. 1043 | 1044 | **Enhanced Readability**: Using getters and setters makes the code more readable and self-explanatory, as the intent and constraints are clearer. 1045 | 1046 | 1047 | 1048 | # Classes 1049 | 1050 | ## Method Chaining 1051 | 1052 | This pattern is very useful in Swift, and you'll find it in many libraries. It allows your code to be expressive and less verbose. For this reason, I say, use method chaining and see how your code becomes cleaner. In your class functions, just return `self` at the end of each function, and you can chain more class methods on it. 1053 | 1054 | ### **Bad:** 1055 | ```swift 1056 | class Car { 1057 | var brand: String 1058 | var speed: Int 1059 | 1060 | init() { 1061 | self.brand = "Generic" 1062 | self.speed = 0 1063 | } 1064 | 1065 | @discardableResult 1066 | func setBrand(_ brand: String) -> Car { 1067 | self.brand = brand 1068 | return self 1069 | } 1070 | 1071 | @discardableResult 1072 | func setSpeed(_ speed: Int) -> Car { 1073 | self.speed = speed 1074 | return self 1075 | } 1076 | 1077 | func printDetails() { 1078 | print("Brand: \(brand), Speed: \(speed)") 1079 | } 1080 | } 1081 | 1082 | // Usage 1083 | let car = Car() 1084 | car.setBrand("Toyota") 1085 | .setSpeed(120) 1086 | .setBrand("") // No validation, invalid brand 1087 | .setSpeed(-10) // No validation, invalid speed 1088 | .printDetails() // Brand: , Speed: -10 1089 | ``` 1090 | ## Summary of Errors in the Bad Example 1091 | 1092 | 1. **Lack of Validation**: Directly setting properties without validation can lead to invalid states (e.g., empty brand or negative speed). 1093 | 2. **Uncontrolled Access**: Public properties allow for unrestricted and potentially unsafe modifications. 1094 | 3. **Inconsistent State Management**: Without proper error handling, invalid values can be set, leading to inconsistencies in the object's state. 1095 | 4. **Reduced Readability**: Method chaining without error handling can hide potential issues, making the code harder to debug and maintain. 1096 | 1097 | 1098 | ### **Good:** 1099 | ```swift 1100 | class Car { 1101 | private var make: String 1102 | private var model: String 1103 | private var color: String 1104 | 1105 | init(make: String, model: String, color: String) { 1106 | self.make = make 1107 | self.model = model 1108 | self.color = color 1109 | } 1110 | 1111 | func setMake(_ make: String) -> Car { 1112 | self.make = make 1113 | return self 1114 | } 1115 | 1116 | func setModel(_ model: String) -> Car { 1117 | self.model = model 1118 | return self 1119 | } 1120 | 1121 | func setColor(_ color: String) -> Car { 1122 | self.color = color 1123 | return self 1124 | } 1125 | 1126 | func save() -> Car { 1127 | print("\(self.make) \(self.model) \(self.color)") 1128 | return self 1129 | } 1130 | } 1131 | 1132 | let car = Car(make: "Ford", model: "F-150", color: "red") 1133 | car.setColor("pink").save() 1134 | ``` 1135 | 1136 | 1137 | 1. **Error Handling**: Using `Result` allows each step in the chain to handle errors gracefully, improving robustness. 1138 | 2. **Encapsulation**: Private members and controlled access through methods ensure data integrity and encapsulation. 1139 | 3. **Fluent Interface**: Method chaining maintains a fluent and readable interface, improving code readability. 1140 | 4. **Consistency**: Consistent use of methods for state changes ensures that all modifications go through the same validation and error handling process. 1141 | 5. **Maintainability**: Encapsulated and validated state changes make the code easier to maintain and extend. 1142 | 1143 | 1144 | ## Prefer Composition Over Inheritance 1145 | As famously stated in the [*Design Patterns*](https://en.wikipedia.org/wiki/Design_Patterns) book by the Gang of Four, you should prefer composition over inheritance where you can. There are many good reasons to use inheritance and many good reasons to use composition. The main point for this maxim is that if your mind instinctively goes for inheritance, try to think if composition could better model your problem. In some cases, it might. 1146 | 1147 | You might be wondering then, "when should I use inheritance?" It depends specifically on your problem, but this is a decent list of when inheritance makes more sense than composition: 1148 | 1149 | 1. Your inheritance represents an "is-a" relationship and not a "has-a" relationship (Human→Animal vs. User->UserDetails). 1150 | 2. You can reuse code from the base classes (Humans can move like all animals). 1151 | 3. You want to make global changes to derived classes by changing only the base class. (Changing the caloric cost for all animals when they move). 1152 | 1153 | ### **Bad:** 1154 | ```swift 1155 | class Employee { 1156 | var name: String 1157 | var email: String 1158 | 1159 | init(name: String, email: String) { 1160 | self.name = name 1161 | self.email = email 1162 | } 1163 | 1164 | // ... 1165 | } 1166 | 1167 | // Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee 1168 | class EmployeeTaxData: Employee { 1169 | var ssn: String 1170 | var salary: Double 1171 | 1172 | init(name: String, email: String, ssn: String, salary: Double) { 1173 | self.ssn = ssn 1174 | self.salary = salary 1175 | super.init(name: name, email: email) 1176 | } 1177 | 1178 | // ... 1179 | } 1180 | ``` 1181 | 1182 | ### **Good:** 1183 | ```swift 1184 | class EmployeeTaxData { 1185 | var ssn: String 1186 | var salary: Double 1187 | 1188 | init(ssn: String, salary: Double) { 1189 | self.ssn = ssn 1190 | self.salary = salary 1191 | } 1192 | 1193 | // ... 1194 | } 1195 | 1196 | class Employee { 1197 | var name: String 1198 | var email: String 1199 | var taxData: EmployeeTaxData? 1200 | 1201 | init(name: String, email: String) { 1202 | self.name = name 1203 | self.email = email 1204 | } 1205 | 1206 | func setTaxData(ssn: String, salary: Double) { 1207 | self.taxData = EmployeeTaxData(ssn: ssn, salary: salary) 1208 | } 1209 | // ... 1210 | } 1211 | ``` 1212 | 1213 | 1214 | # SOLID 1215 | 1216 | ## Single Responsibility Principle (SRP) 1217 | As stated in *Clean Code*, "There should never be more than one reason for a class to change." It's tempting to pack a class with many functionalities, like when you can only bring one suitcase on your flight. The problem with this is that your class will not be conceptually cohesive and will give it multiple reasons to change. Minimizing the number of times you need to change a class is important because if many functionalities are in one class and you change a portion of it, it can be difficult to understand how it will affect other modules that depend on it in your code. 1218 | 1219 | ### **Bad:** 1220 | ```swift 1221 | class UserSettings { 1222 | var user: User 1223 | 1224 | init(user: User) { 1225 | self.user = user 1226 | } 1227 | 1228 | func changeSettings(settings: Settings) { 1229 | if self.verifyCredentials() { 1230 | // ... 1231 | } 1232 | } 1233 | 1234 | func verifyCredentials() -> Bool { 1235 | // ... 1236 | } 1237 | } 1238 | ``` 1239 | 1240 | ### **Good:** 1241 | ```swift 1242 | class UserAuth { 1243 | var user: User 1244 | 1245 | init(user: User) { 1246 | self.user = user 1247 | } 1248 | 1249 | func verifyCredentials() -> Bool { 1250 | // ... 1251 | } 1252 | } 1253 | 1254 | class UserSettings { 1255 | var user: User 1256 | var auth: UserAuth 1257 | 1258 | init(user: User) { 1259 | self.user = user 1260 | self.auth = UserAuth(user: user) 1261 | } 1262 | 1263 | func changeSettings(settings: Settings) { 1264 | if self.auth.verifyCredentials() { 1265 | // ... 1266 | } 1267 | } 1268 | } 1269 | ``` 1270 | 1271 | 1272 | ## Open/Closed Principle (OCP) 1273 | As stated by Bertrand Meyer, "Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification." But what does that mean? This principle basically says that you should allow users to add new functionalities without changing existing code. 1274 | 1275 | ### **Bad:** 1276 | ```swift 1277 | class SwiftAdapter: Adapter { 1278 | override init() { 1279 | super.init() 1280 | self.name = "SwiftAdapter" 1281 | } 1282 | } 1283 | 1284 | class ObjcAdapter: Adapter { 1285 | override init() { 1286 | super.init() 1287 | self.name = "ObjcAdapter" 1288 | } 1289 | } 1290 | 1291 | class HttpRequester { 1292 | var adapter: Adapter 1293 | 1294 | init(adapter: Adapter) { 1295 | self.adapter = adapter 1296 | } 1297 | 1298 | func fetch(url: String) -> Promise { 1299 | if self.adapter.name == "SwiftAdapter" { 1300 | return makeSwiftCall(url: url).then { response in 1301 | // transform the response and return 1302 | } 1303 | } else if self.adapter.name == "httpObjcAdapter" { 1304 | return make 1305 | 1306 | HttpCall(url: url).then { response in 1307 | // transform the response and return 1308 | } 1309 | } 1310 | fatalError("Adapter not supported") 1311 | } 1312 | } 1313 | 1314 | func makeSwiftCall(url: String) -> Promise { 1315 | // make the request and return the promise 1316 | } 1317 | 1318 | func makeHttpCall(url: String) -> Promise { 1319 | // make the request and return the promise 1320 | } 1321 | ``` 1322 | 1323 | ### **Good:** 1324 | ```swift 1325 | class SwiftAdapter: Adapter { 1326 | override init() { 1327 | super.init() 1328 | self.name = "SwiftAdapter" 1329 | } 1330 | 1331 | func request(url: String) -> Promise { 1332 | // make the request and return the promise 1333 | } 1334 | } 1335 | 1336 | class ObjcAdapter: Adapter { 1337 | override init() { 1338 | super.init() 1339 | self.name = "ObjcAdapter" 1340 | } 1341 | 1342 | func request(url: String) -> Promise { 1343 | // make the request and return the promise 1344 | } 1345 | } 1346 | 1347 | class HttpRequester { 1348 | var adapter: Adapter 1349 | 1350 | init(adapter: Adapter) { 1351 | self.adapter = adapter 1352 | } 1353 | 1354 | func fetch(url: String) -> Promise { 1355 | return self.adapter.request(url: url).then { response in 1356 | // transform the response and return 1357 | } 1358 | } 1359 | } 1360 | ``` 1361 | 1362 | 1363 | ## Liskov Substitution Principle (LSP) 1364 | This is a scary term for a very simple concept. It's formally defined as "If S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S may substitute objects of type T) without altering any of the desirable properties of a program (correctness, task performance, etc.)." That's an even scarier definition. 1365 | 1366 | The best explanation for this concept is if you have a parent class and a child class, then the base class and the child class can be used interchangeably without getting incorrect results. This might still be confusing, so let's look at the classic example of the Square-Rectangle problem. Mathematically, a square is a rectangle, but if you model it using an "is-a" relationship through inheritance, you quickly run into problems. 1367 | 1368 | ### **Bad:** 1369 | ```swift 1370 | class Rectangle { 1371 | var width: Double 1372 | var height: Double 1373 | 1374 | init() { 1375 | self.width = 0 1376 | self.height = 0 1377 | } 1378 | 1379 | func setColor(color: String) { 1380 | // ... 1381 | } 1382 | 1383 | func render(area: Double) { 1384 | // ... 1385 | } 1386 | 1387 | func setWidth(width: Double) { 1388 | self.width = width 1389 | } 1390 | 1391 | func setHeight(height: Double) { 1392 | self.height = height 1393 | } 1394 | 1395 | func getArea() -> Double { 1396 | return self.width * self.height 1397 | } 1398 | } 1399 | 1400 | class Square: Rectangle { 1401 | override func setWidth(width: Double) { 1402 | self.width = width 1403 | self.height = width 1404 | } 1405 | 1406 | override func setHeight(height: Double) { 1407 | self.width = height 1408 | self.height = height 1409 | } 1410 | } 1411 | 1412 | func renderLargeRectangles(rectangles: [Rectangle]) { 1413 | rectangles.forEach { rectangle in 1414 | rectangle.setWidth(width: 4) 1415 | rectangle.setHeight(height: 5) 1416 | let area = rectangle.getArea() // BAD: Returns 25 for the Square. Should be 20. 1417 | rectangle.render(area: area) 1418 | } 1419 | } 1420 | 1421 | let rectangles: [Rectangle] = [Rectangle(), Rectangle(), Square()] 1422 | renderLargeRectangles(rectangles: rectangles) 1423 | ``` 1424 | 1425 | ### **Good:** 1426 | ```swift 1427 | class Shape { 1428 | func setColor(color: String) { 1429 | // ... 1430 | } 1431 | 1432 | func render(area: Double) { 1433 | // ... 1434 | } 1435 | } 1436 | 1437 | class Rectangle: Shape { 1438 | var width: Double 1439 | var height: Double 1440 | 1441 | init(width: Double, height: Double) { 1442 | self.width = width 1443 | self.height = height 1444 | } 1445 | 1446 | func getArea() -> Double { 1447 | return self.width * self.height 1448 | } 1449 | } 1450 | 1451 | class Square: Shape { 1452 | var length: Double 1453 | 1454 | init(length: Double) { 1455 | self.length = length 1456 | } 1457 | 1458 | func getArea() -> Double { 1459 | return self.length * self.length 1460 | } 1461 | } 1462 | 1463 | func renderLargeShapes(shapes: [Shape]) { 1464 | shapes.forEach { shape in 1465 | let area = shape.getArea() 1466 | shape.render(area: area) 1467 | } 1468 | } 1469 | 1470 | let shapes: [Shape] = [Rectangle(width: 4, height: 5), Rectangle(width: 4, height: 5), Square(length: 5)] 1471 | renderLargeShapes(shapes: shapes) 1472 | ``` 1473 | 1474 | 1475 | ## Interface Segregation Principle (ISP) with Protocols 1476 | Although Swift doesn't adopt the traditional concept of interfaces, we can apply the Interface Segregation Principle (ISP) through protocols, leveraging Swift's flexibility. 1477 | 1478 | ISP states that "Clients should not be forced to depend on interfaces they do not use." In Swift, where duck typing prevails, we can create protocols that represent segregated interfaces. 1479 | 1480 | ### **Bad:** 1481 | ```swift 1482 | // Single protocol with many requirements 1483 | protocol Styling { 1484 | var font: UIFont { get } 1485 | var backgroundColor: UIColor { get } 1486 | var cornerRadius: CGFloat { get } 1487 | 1488 | func applyStyles() 1489 | } 1490 | 1491 | // Protocol implementation 1492 | class BadStylableView: Styling { 1493 | var font: UIFont 1494 | var backgroundColor: UIColor 1495 | var cornerRadius: CGFloat 1496 | 1497 | init(font: UIFont, backgroundColor: UIColor, cornerRadius: CGFloat) { 1498 | self.font = font 1499 | self.backgroundColor = backgroundColor 1500 | self.cornerRadius = cornerRadius 1501 | } 1502 | 1503 | func applyStyles() { 1504 | // Apply styles based on configuration 1505 | } 1506 | } 1507 | 1508 | // Example of usage 1509 | let badView = BadStylableView(font: .systemFont(ofSize: 14), backgroundColor: .white, cornerRadius: 8) 1510 | ``` 1511 | 1512 | ### **Good:** 1513 | ```swift 1514 | // Protocol for styling configuration 1515 | protocol StyleConfigurable { 1516 | var font: UIFont { get } 1517 | var backgroundColor: UIColor { get } 1518 | var cornerRadius: CGFloat { get } 1519 | } 1520 | 1521 | // Protocol for applying styles 1522 | protocol StyleApplicable { 1523 | func applyStyles() 1524 | } 1525 | 1526 | // Default implementation for styling configuration 1527 | struct AppearanceConfig: StyleConfigurable { 1528 | var font: UIFont 1529 | var backgroundColor: UIColor 1530 | var cornerRadius: CGFloat 1531 | } 1532 | 1533 | // View adopting the protocols 1534 | class GoodStylableView: StyleApplicable { 1535 | var styleConfig: StyleConfigurable 1536 | 1537 | init(styleConfig: StyleConfigurable) { 1538 | self.styleConfig = styleConfig 1539 | self.setup() 1540 | } 1541 | 1542 | func setup() { 1543 | applyStyles() 1544 | } 1545 | 1546 | func applyStyles() { 1547 | // Apply styles based on configuration 1548 | } 1549 | } 1550 | 1551 | // Example of usage 1552 | let goodView = GoodStylableView(styleConfig: AppearanceConfig(font: .systemFont(ofSize: 14))) 1553 | ``` 1554 | 1555 | In the bad example, a single `Styling` protocol contains many requirements, forcing clients to implement methods and properties that may not be necessary. In the good example, we use segregated protocols (`StyleConfigurable` and `StyleApplicable`) to allow for a more specific and flexible implementation. 1556 | 1557 | 1558 | ## Dependency Inversion Principle (DIP) 1559 | This principle tells us two essential things: 1560 | 1561 | 1. High-level modules should not depend on low-level modules. Both should depend on abstractions. 1562 | 2. Abstractions should not depend on details. Details should depend on abstractions. 1563 | 1564 | This might be 1565 | 1566 | hard to understand at first, but if you've worked with AngularSwift, you've seen an implementation of this principle in the form of Dependency Injection (DI). While they are not identical concepts, DIP does not allow high-level modules to know the details of their low-level modules but configures them. This can be achieved through DI. A significant benefit is that it reduces coupling between modules. Coupling is a very bad development pattern because it makes your code harder to refactor. 1567 | 1568 | As mentioned earlier, Swift doesn't have interfaces, so the abstractions needed are implicit contracts. That means the methods and classes that an object/class exposes to other objects/classes. In the example below, the implicit contract is that any Request module for `InventoryTracker` will have a `requestItems` method: 1569 | 1570 | ### **Bad:** 1571 | ```swift 1572 | class InventoryRequester { 1573 | init() { 1574 | self.REQ_METHODS = ["HTTP"] 1575 | } 1576 | 1577 | func requestItem(item: String) { 1578 | // ... 1579 | } 1580 | } 1581 | 1582 | class InventoryTracker { 1583 | var items: [String] 1584 | var requester: InventoryRequester 1585 | 1586 | init(items: [String]) { 1587 | self.items = items 1588 | 1589 | // Bad: We created a dependency on a specific request implementation. 1590 | // We should only have requestItems depend on a request method: `request` 1591 | self.requester = InventoryRequester() 1592 | } 1593 | 1594 | func requestItems() { 1595 | self.items.forEach { item in 1596 | self.requester.requestItem(item: item) 1597 | } 1598 | } 1599 | } 1600 | 1601 | let inventoryTracker = InventoryTracker(items: ["apples", "bananas"]) 1602 | inventoryTracker.requestItems() 1603 | ``` 1604 | 1605 | ### **Good:** 1606 | ```swift 1607 | class InventoryTracker { 1608 | var items: [String] 1609 | var requester: RequesterProtocol 1610 | 1611 | init(items: [String], requester: RequesterProtocol) { 1612 | self.items = items 1613 | self.requester = requester 1614 | } 1615 | 1616 | func requestItems() { 1617 | self.items.forEach { item in 1618 | self.requester.requestItem(item: item) 1619 | } 1620 | } 1621 | } 1622 | 1623 | protocol RequesterProtocol { 1624 | func requestItem(item: String) 1625 | } 1626 | 1627 | class InventoryRequesterV1: RequesterProtocol { 1628 | func requestItem(item: String) { 1629 | // ... 1630 | } 1631 | } 1632 | 1633 | class InventoryRequesterV2: RequesterProtocol { 1634 | func requestItem(item: String) { 1635 | // ... 1636 | } 1637 | } 1638 | 1639 | // Building our dependencies externally and injecting them, we can easily 1640 | // swap our request module for a new fancy one that uses WebSockets 1641 | let inventoryTracker = InventoryTracker(items: ["apples", "bananas"], requester: InventoryRequesterV2()) 1642 | inventoryTracker.requestItems() 1643 | ``` 1644 | 1645 | 1646 | # Testing 1647 | Tests are more critical than shipping. If you have no tests or an inadequate amount, then every time you ship code, you won't be sure if you didn't break anything. Deciding what constitutes an adequate amount is up to your team, but having 100% coverage (all statements and branches) is how you achieve very high confidence and peace of mind. This means that in addition to having a great testing framework, you also need to use a [good coverage tool](https://www.sonarsource.com/products/sonarqube/). 1648 | 1649 | There's no excuse for not writing tests. There are [many great testing frameworks for Swift](https://github.com/vsouza/awesome-ios#testing), so find one that your team prefers. When you find one that works for your team, then aim to always write tests for every new feature/module you introduce. If your preferred method is Test-Driven Development (TDD), that's great, but the main point is to ensure you are reaching your coverage goals before launching any feature or refactoring an existing one. 1650 | 1651 | ## One Concept per Test 1652 | 1653 | ### **Bad:** 1654 | ```swift 1655 | import XCTest 1656 | 1657 | class MakeMomentSwiftGreatAgainTests: XCTestCase { 1658 | 1659 | func testHandlesDateBoundaries() { 1660 | var date = MakeMomentSwiftGreatAgain("1/1/2015") 1661 | date.addDays(30) 1662 | XCTAssertEqual("1/31/2015", date) 1663 | 1664 | date = MakeMomentSwiftGreatAgain("2/1/2016") 1665 | date.addDays(28) 1666 | XCTAssertEqual("02/29/2016", date) 1667 | 1668 | date = MakeMomentSwiftGreatAgain("2/1/2015") 1669 | date.addDays(28) 1670 | XCTAssertEqual("03/01/2015", date) 1671 | } 1672 | } 1673 | ``` 1674 | 1675 | ### **Good:** 1676 | ```swift 1677 | import XCTest 1678 | 1679 | class MakeMomentSwiftGreatAgainTests: XCTestCase { 1680 | 1681 | func testHandles30DayMonths() { 1682 | let date = MakeMomentSwiftGreatAgain("1/1/2015") 1683 | date.addDays(30) 1684 | XCTAssertEqual("1/31/2015", date) 1685 | } 1686 | 1687 | func testHandlesLeapYear() { 1688 | let date = MakeMomentSwiftGreatAgain("2/1/2016") 1689 | date.addDays(28) 1690 | XCTAssertEqual("02/29/2016", date) 1691 | } 1692 | 1693 | func testHandlesNonLeapYear() { 1694 | let date = MakeMomentSwiftGreatAgain("2/1/2015") 1695 | date.addDays(28) 1696 | XCTAssertEqual("03/01/2015", date) 1697 | } 1698 | } 1699 | ``` 1700 | 1701 | 1702 | # Concurrency 1703 | ## Async/Await is even cleaner than Promises 1704 | After iOS 13, Swift introduces `async` and `await` that offer an even cleaner solution. All you need is a function prefixed with the `async` keyword, and then you can write your logic imperatively without using `completions` to chain your functions. Use this if you can take advantage of Swift's features today! 1705 | 1706 | ### **Bad:** 1707 | ```swift 1708 | import Foundation 1709 | 1710 | let url = URL(string: "https://en.wikipedia.org/wiki/Robert_Cecil_Martin")! 1711 | 1712 | URLSession.shared.dataTask(with: url) { (data, response, error) in 1713 | guard let data = data, error == nil else { 1714 | print(error?.localizedDescription ?? "Unknown error") 1715 | return 1716 | } 1717 | 1718 | do { 1719 | try data.write(to: URL(fileURLWithPath: "article.html")) 1720 | print("File written") 1721 | } catch { 1722 | print(error.localizedDescription) 1723 | } 1724 | }.resume() 1725 | ``` 1726 | 1727 | ### **Good:** 1728 | ```swift 1729 | import Foundation 1730 | 1731 | async func getCleanCodeArticle() { 1732 | let url = URL(string: "https://en.wikipedia.org/wiki/Robert_Cecil_Martin")! 1733 | 1734 | do { 1735 | let (data, _) = try await URLSession.shared.data(from: url) 1736 | try await data.write(to: URL(fileURLWithPath: "article.html")) 1737 | print("File written") 1738 | } catch { 1739 | print(error.localizedDescription) 1740 | } 1741 | } 1742 | 1743 | Task { 1744 | await getCleanCodeArticle() 1745 | } 1746 | ``` 1747 | 1748 | 1749 | 1750 | # Error Handling 1751 | `throw error` is a good thing! They mean the program has successfully identified when something went wrong and is letting you know by halting the execution of the current function, unwinding the process (in Swift), and notifying you in the console with the process stack. 1752 | 1753 | ## Don't ignore caught errors 1754 | Doing nothing with a caught error doesn't give you the ability to address it or react to the reported error. Just printing a log to the console (`print`) is not much better because it can often get lost among a bunch of other things printed to the console. If you wrap any piece of code in a `do/catch`, it means you believe an error might occur there and then you should have a plan, or create a code path for when it happens. 1755 | 1756 | ### **Bad:** 1757 | ```swift 1758 | do { 1759 | try funcThatMightThrow() 1760 | } catch { 1761 | print(error) 1762 | } 1763 | ``` 1764 | 1765 | ### **Good:** 1766 | ```swift 1767 | do { 1768 | try funcThatMightThrow() 1769 | } catch { 1770 | // One option (more noticeable than print): 1771 | print(error) 1772 | // Another option: 1773 | notifyUserOfError(error) 1774 | // Another option: 1775 | reportErrorToService(error) 1776 | // OR all three! 1777 | } 1778 | ``` 1779 | 1780 | # Formatting 1781 | 1782 | Formatting is subjective. Like many of the rules here, there's no hard and fast rule you need to follow. The main idea is NOT to argue about formatting. There are tools that automate this process; let them handle it. Most Swift code should follow the [Google Swift Style Guide](https://google.github.io/swift/). 1783 | 1784 | ## Use spaces instead of tabs 1785 | Despite the epic struggle between spaces and tabs, the important thing is to be consistent. Swift uses spaces, and it's a common practice in other Swift projects. Do the same. 1786 | 1787 | ### **Bad:** 1788 | ```swift 1789 | func bad() { 1790 | ∙∙∙∙var name:String? 1791 | ∙∙∙∙guard let unwrappedName = name else { 1792 | ∙∙∙∙∙∙∙return 1793 | ∙∙∙∙} 1794 | } 1795 | ``` 1796 | 1797 | ### **Good:** 1798 | ```swift 1799 | func good() { 1800 | var name: String? 1801 | guard let unwrappedName = name else { 1802 | return 1803 | } 1804 | } 1805 | ``` 1806 | 1807 | ## Use blank lines effectively 1808 | Separating code blocks with blank lines can make the code more readable and organized. However, excessive blank lines can have the opposite effect, creating a sense of fragmentation. Use blank lines sparingly. 1809 | 1810 | ### **Bad:** 1811 | ```swift 1812 | func calculateTotalScore(score: Int) { 1813 | 1814 | var totalScore = 0 1815 | 1816 | for i in 1...score { 1817 | 1818 | totalScore += i 1819 | 1820 | } 1821 | 1822 | print("The total score is: \(totalScore)") 1823 | 1824 | } 1825 | ``` 1826 | 1827 | ### **Good:** 1828 | ```swift 1829 | func calculateTotalScore(score: Int) { 1830 | var totalScore = 0 1831 | 1832 | for i in 1...score { 1833 | totalScore += i 1834 | } 1835 | 1836 | print("The total score is: \(totalScore)") 1837 | } 1838 | ``` 1839 | 1840 | ## Limit line length 1841 | The recommended length for a code line is 80 characters. This ensures that you don't have to scroll horizontally to read the code. It's common in many development environments to have two files side by side. Making this possible makes reading the code easier. 1842 | 1843 | ### **Bad:** 1844 | ```swift 1845 | let errorMessage = "This is a very long error message that exceeds the recommended line length of 80 characters and makes the code harder to read." 1846 | ``` 1847 | 1848 | ### **Good:** 1849 | ```swift 1850 | let errorMessage = "This is a short error message." 1851 | ``` 1852 | 1853 | ## Use consistent spacing 1854 | Maintaining consistency in the use of spaces around operators and after commas contributes to code readability. 1855 | 1856 | ### **Bad:** 1857 | ```swift 1858 | let sum = 1+2 1859 | let array = [1 , 2,3, 4] 1860 | ``` 1861 | 1862 | ### **Good:** 1863 | ```swift 1864 | let sum = 1 + 2 1865 | let array = [1, 2, 3, 4] 1866 | ``` 1867 | 1868 | ## Avoid excessive whitespace 1869 | Excessive whitespace can visually clutter the code and make it less readable. Maintain a moderate use of whitespace. 1870 | 1871 | ### **Bad:** 1872 | ```swift 1873 | func foo ( a : Int , b : Int ) -> Int { 1874 | return a + b 1875 | } 1876 | ``` 1877 | 1878 | ### **Good:** 1879 | ```swift 1880 | func foo(a: Int, b: Int) -> Int { 1881 | return a + b 1882 | } 1883 | ``` 1884 | 1885 | ## Follow the formatting recommended by SwiftLint 1886 | Code formatting can be a controversial topic, but it's important to maintain a consistent standard within your project. SwiftLint is a helpful tool for enforcing formatting standards. Integrate SwiftLint into your workflow to ensure the code follows recommended practices. 1887 | 1888 | 1889 | # Comments 1890 | 1891 | ## Only comment on things that have business logic complexity. 1892 | Comments should be used to explain parts of the code that have non-obvious complexity or to provide additional information about business logic. Avoid commenting on the obvious or trivial details that can be easily understood by reading the code. 1893 | 1894 | ### **Bad:** 1895 | ```swift 1896 | func calculateTotalScore(score: Int) { 1897 | // Initialize the total score 1898 | var totalScore = 0 1899 | 1900 | // Loop through each individual score 1901 | for i in 1...score { 1902 | // Add the individual score to the total score 1903 | totalScore += i 1904 | } 1905 | 1906 | // Print the total score 1907 | print("The total score is: \(totalScore)") 1908 | } 1909 | ``` 1910 | 1911 | ### **Good:** 1912 | ```swift 1913 | func calculateTotalScore(score: Int) { 1914 | // The total score is calculated using the Gauss sum formula 1915 | var totalScore = (score * (score + 1)) / 2 1916 | 1917 | // Print the total score 1918 | print("The total score 1919 | 1920 | is: \(totalScore)") 1921 | } 1922 | ``` 1923 | 1924 | 1925 | ## Don't leave commented-out code in your codebase 1926 | Version control (like Git) is a powerful tool for tracking changes over time. There's no need to keep commented-out code in the codebase as it only adds noise and makes it harder to read. 1927 | 1928 | ### **Bad:** 1929 | ```swift 1930 | func doSomething() { 1931 | // Previous code that is no longer needed 1932 | // ... 1933 | 1934 | // Code commented out for future reference 1935 | /* 1936 | if someCondition { 1937 | // Code to be executed 1938 | } 1939 | */ 1940 | 1941 | // ... 1942 | } 1943 | ``` 1944 | 1945 | ### **Good:** 1946 | ```swift 1947 | func doSomething() { 1948 | // Current code 1949 | // ... 1950 | } 1951 | ``` 1952 | 1953 | 1954 | ## Avoid change markers 1955 | Avoid using change markers, such as slashes or lines of asterisks, to divide or highlight sections of code. Instead, use good code structure with proper indentation and formatting to make the code easily understandable. 1956 | 1957 | ### **Bad:** 1958 | ```swift 1959 | struct Example { 1960 | // MARK: - Properties 1961 | var name: String 1962 | var age: Int 1963 | 1964 | // MARK - Initializer 1965 | init(name: String, age: Int) { 1966 | self.name = name 1967 | self.age = age 1968 | } 1969 | 1970 | // MARK - Functions 1971 | 1972 | // Method to perform an action 1973 | func performAction() { 1974 | // ... 1975 | } 1976 | } 1977 | ``` 1978 | 1979 | ### **Good:** 1980 | ```swift 1981 | struct Example { 1982 | var name: String 1983 | var age: Int 1984 | 1985 | init(name: String, age: Int) { 1986 | self.name = name 1987 | self.age = age 1988 | } 1989 | 1990 | func performAction() { 1991 | // ... 1992 | } 1993 | } 1994 | ``` 1995 | 1996 | --------------------------------------------------------------------------------