Skip to content

Instantly share code, notes, and snippets.

@scott-lydon
Last active July 31, 2024 06:52
Show Gist options
  • Save scott-lydon/3517b7b9f1829845faed826a63bfee76 to your computer and use it in GitHub Desktop.
Save scott-lydon/3517b7b9f1829845faed826a63bfee76 to your computer and use it in GitHub Desktop.

Coding Guidelines

Rules

  1. Omit the return keyword in one-line functions that return a value.
  2. Use trailing closure syntax.
  3. Remove the Google indentation guide rule due to lack of specific guidelines in the Google Swift style guide and because SwiftLint handles indentation.
  4. Reevaluate the rule regarding DispatchQueue from inaction on the main thread for feasibility in static analysis.
  5. Use higher-order functions when multiple properties call the same function.
  6. Require every method to have a comment.
  7. Remove one-line functions that do not return anything and call the code directly.
  8. Avoid print statements; use a logging function instead (as a warning).
  9. Avoid function declarations within other function bodies.
  10. Use higher-order functions for processing arrays instead of loops.
  11. Avoid using [String: Any]; use Codable instead.
  12. Combine nested if-else statements into a single initialization statement.
  13. Avoid empty files.
  14. Inline constants or variables used only once.
  15. query in a Vapor Model

Removed Rules and Reasons

  1. Google Indentation Guide: Removed because there is no specific section on indentation in the Google Swift style guide, and SwiftLint already handles indentation.
  2. DispatchQueue from inaction is already on main thread: Reevaluate the feasibility of enforcing this rule statically. Consider dynamic analysis or runtime checks to ensure code runs on the correct thread.

Examples

1. Omit the return keyword in one-line functions that return a value.

Non-triggering examples:

func blocks() -> [Block] {
    .all(with: .retained, times: 2)
}
func fetchUser() -> User {
    User(name: "John")
}
func calculateSum(a: Int, b: Int) -> Int {
    a + b
}

Triggering examples:

func blocks() -> [Block] { return .all(with: .retained, times: 2) }
func fetchUser() -> User { return User(name: "John") }
func calculateSum(a: Int, b: Int) -> Int { return a + b }

2. Use trailing closure syntax.

Non-triggering examples:

someFunction { value in
    print(value)
}
UIView.animate(withDuration: 0.3) {
    self.view.alpha = 1.0
}
fetchData { result in
    handle(result)
}

Triggering examples:

someFunction(settingsAction: { value in
    print(value)
})
UIView.animate(withDuration: 0.3, animations: {
    self.view.alpha = 1.0
})
fetchData(completion: { result in
    handle(result)
})

3. Removed.

4. Use higher-order functions when multiple properties call the same function.

Non-triggering examples:

[am6Btn, am7Btn].forEach { $0.titleLabel?.setAkinFont() }
[btn1, btn2, btn3].forEach { $0.isEnabled = true }
[view1, view2, view3].forEach { $0.isHidden = false }

Triggering examples:

am6Btn.titleLabel?.setAkinFont()
am7Btn.titleLabel?.setAkinFont()
btn1.isEnabled = true
btn2.isEnabled = true
btn3.isEnabled = true
view1.isHidden = false
view2.isHidden = false
view3.isHidden = false

More examples:

addButton.addTarget(
    self,
    action: #selector(addProfilePicPress),
    for: .touchUpInside
)
addProfilePicButton.addTarget(
    self,
    action: #selector(addProfilePicPress),
    for: .touchUpInside
)

Should be:

[addButton, addProfilePicButton].forEach {
    $0.addTarget(self, action: #selector(addProfilePicPress), for: .touchUpInside)
}
logoutLabel.text = "Logout"
logoutLabel.textColor = .romanceRed
contentView.addSubview(logoutLabel)
logoutLabel.constraint(from: .centeredHorizontallyWith(contentView))
logoutLabel.constraint(from: .centeredVerticallyTo(contentView))
logoutLabel.constraint(from: .distanceToBottom(contentView.bottomAnchor, 36))
logoutLabel.constraint(from: .distanceToTop(contentView.topAnchor, 36))

Should be:

logoutLabel.text = "Logout"
logoutLabel.textColor = .romanceRed
contentView.addSubview(logoutLabel)
[.centeredHorizontallyWith(contentView), .centeredVerticallyTo(contentView),
 .distanceToBottom(contentView.bottomAnchor, 36), .distanceToTop(contentView.topAnchor, 36)].forEach {
    logoutLabel.constraint(from: $0)
}
rightAddButton.titleLabel?.setAkinFont()
scrollButton.titleLabel?.setAkinFont()
closeButton.titleLabel?.setAkinFont()
closeButtonBordered.titleLabel?.setAkinFont()

Should be:

[rightAddButton, scrollButton, closeButton, closeButtonBordered].forEach {
    $0.titleLabel?.setAkinFont()
}

5. Require every method to have a comment.

Non-triggering examples:

/// Fetches data from the server.
func fetchData() {
    // implementation
}

/// Handles user login.
func loginUser() {
    // implementation
}

Triggering examples:

func fetchData() {
    // implementation
}

func loginUser() {
    // implementation
}

6. Remove one-line functions that do not return anything and call the code directly.

Non-triggering examples:

print("Hello, World!")

Triggering examples:

func printHello() {
    print("Hello, World!")
}
func reset() {
    value = 0
}

7. Avoid print statements; use a logging function instead.

Non-triggering examples:

logger.log("This is a log message")
Log.info("User logged in successfully")
debugPrint("Debugging information")

Triggering examples:

print("This is a log message")
print("User logged in successfully")
print("Debugging information")

8. Avoid function declarations within other function bodies.

Non-triggering examples:

func outerFunction() {
    // code
}

func innerFunction() {
    // code
}
func calculate() {
    let result = compute()
    // other code
}
func performAction() {
    // action code
}

Triggering examples:

func outerFunction() {
    func innerFunction() {
        // code
    }
    innerFunction()
}
func calculate() {
    func compute() -> Int {
        return 5
    }
    let result = compute()
}
func performAction() {
    func action() {
        // action code
    }
    action()
}

9. Use higher-order functions for processing arrays instead of loops.

Non-triggering examples:

let values = ["a", "b", "c"].map { $0.uppercased() }
let filtered = numbers.filter { $0 > 10 }
let sum = numbers.reduce(0, +)

Triggering examples:

var values = [String]()
for item in ["a", "b", "c"] {
    values.append(item.uppercased())
}
var filtered = [Int]()
for number in numbers {
    if number > 10 {
        filtered.append(number)
    }
}
var sum = 0
for number in numbers {
    sum += number
}

10. Avoid using [String: Any]; use Codable instead.

Non-triggering examples:

struct MyData: Codable {
    let name: String
    let age: Int
}
let jsonData = try? JSONEncoder().encode(myData)
let myData = try? JSONDecoder().decode(MyData.self, from: jsonData)

Triggering examples:

let data: [String: Any] = ["name": "John", "age": 30]
let jsonData = try? JSONSerialization.data(withJSONObject: data)
let data = try? JSONSerialization.jsonObject(with: jsonData)

11. Combine nested if-else statements into a single initialization statement.

Non-triggering examples:

self.init(
    presentation: enabled ? .enabled(importance ?? .irrelevant) : .disabled(importance),
    auto
changedImportance: enabled && importance != nil
)

Triggering examples:

if enabled {
    if let importance = importance {
        self.init(presentation: .enabled(importance), autoChangedImportance: false)
    } else {
        self.init(presentation: .enabled(.irrelevant), autoChangedImportance: true)
    }
} else {
    self.init(presentation: .disabled(importance), autoChangedImportance: false)
}

12. Avoid empty files.

Non-triggering examples:

  • Any file with content.

Triggering examples:

  • Any file that is completely empty.

13. Inline constants or variables used only once.

Non-triggering examples:

alertViewController.styleCancelAlert(
    regularContentsModel: RegularContentsView.Model(
        title: "Are you sure?",
        message: "If you close this greet, it can't be reopened."
    ),
    models: UIButton.SimpleModel(
        title: "Yes close it.",
        action: {
            self?.refuseGreetAndClose()
        }
    )
)

Triggering examples:

let contents = RegularContentsView.Model(
    title: "Are you sure?",
    message: "If you close this greet, it can't be reopened."
)
let buttonModel = UIButton.SimpleModel(
    title: "Yes close it.",
    action: {
        self?.refuseGreetAndClose()
    }
)
alertViewController.styleCancelAlert(
    regularContentsModel: contents,
    models: buttonModel
)

14. Inline constants or variables used only once.

Non-triggering examples:

alertViewController.styleCancelAlert(
    regularContentsModel: RegularContentsView.Model(
        title: "Are you sure?",
        message: "If you close this greet, it can't be reopened."
    ),
    models: UIButton.SimpleModel(
        title: "Yes close it.",
        action: {
            self?.refuseGreetAndClose()
        }
    )
)

Triggering examples:

let contents = RegularContentsView.Model(
    title: "Are you sure?",
    message: "If you close this greet, it can't be reopened."
)
let buttonModel = UIButton.SimpleModel(
    title: "Yes close it.",
    action: {
        self?.refuseGreetAndClose()
    }
)
alertViewController.styleCancelAlert(
    regularContentsModel: contents,
    models: buttonModel
)
  1. query in a Vapor Model Non-triggering examples:
extension Database {

    func updateAll(_ models: any Model...) async throws {
        try await transaction { database in
            for model in models {
                try await model.update(on: database)
            }
        }
    }

    // MARK: - User queries

    func sampleUsers(count: Int = .statisticalSignificance + 1) async throws -> [User] {
        try await User.query(on: self)
            .sort(.custom("RANDOM()"))
            .limit(count)
            .all()
    }

Triggering examples:

import Vapor
import Fluent
import JWT
import AkinFrontBackModels

final class User:
    Model,
    Content,
    Authenticatable,
    HasLongitude,
    HasLatitude,
    HasProfilePicture,
    HasUUID,
    HasCreatedAt {
    
    //- ... - // 
    
    // MARK: - User queries

    static func sampleUsers(count: Int = .statisticalSignificance + 1) async throws -> [User] {
        try await query(on: self)
            .sort(.custom("RANDOM()"))
            .limit(count)
            .all()
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment