Skip to content
academia.sh

Lesson 12 / 19

Code Quality Tools

The duties and limits of formatters and static analysis rules; measuring whether a format check and a rule that scans the source text can tell the same fault apart, and how a false positive spoils a check.

Published Updated

In this lesson

  • Distinguishes what a formatter checks from what a static analysis rule checks
  • Measures that a format check cannot see a fault such as a force unwrap
  • Shows that a rule that scans the source text separates the fault
  • Explains that a rule giving false positives on safe use makes the check noisy and that an exemption fixes this
Contents

In the previous lesson (Launch and Scrolling Performance) the fault was in the amount of a measured piece of work. In this lesson the fault is in the source text itself: the code compiles, the tests pass, but under the right conditions one line crashes the app. Code quality tools check the code by looking at its text, without running it.

There are two kinds of tool. A formatter arranges spaces, indentation and line endings; it does not look at what the code does. A static analysis tool reads the source text and finds structures that do not follow the defined rules: an unused variable, a function that is too long, a force-unwrapped optional value. Both make the team write in a common style; but the faults they catch are different.

The measurement axis is separation again: we apply the same check to the correct version and to the faulty version. A separating check passes on the correct version and fails on the faulty one. A blind check passes on both. A noisy check fails on both. An inverted check fails on the correct version and passes on the faulty one. The core is the same as in the previous lessons; we give it again because each lesson runs on its own.

// Separation.swift — classifies whether a check tells the correct version from the faulty one
struct Contrast<Output: CustomStringConvertible> {
    let label: String
    let correct: Output
    let faulty: Output
    let passesCorrect: Bool
    let passesFaulty: Bool

    var separation: String {
        switch (passesCorrect, passesFaulty) {
        case (true, false): return "separating"
        case (true, true): return "blind"
        case (false, false): return "noisy"
        case (false, true): return "inverted"
        }
    }

    func describe() -> String {
        let onCorrect = passesCorrect ? "pass" : "fail"
        let onFaulty = passesFaulty ? "pass" : "fail"
        print("\(label) | correct=\(correct)/\(onCorrect) faulty=\(faulty)/\(onFaulty) -> \(separation)")
        return separation
    }
}

func contrast<Output>(_ label: String, correct: Output, faulty: Output, check: (Output) -> Bool) -> Contrast<Output> {
    Contrast(label: label, correct: correct, faulty: faulty, passesCorrect: check(correct), passesFaulty: check(faulty))
}

func countSeparations(_ results: [String]) {
    for name in ["separating", "blind", "noisy", "inverted"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}

Contrast holds the output of the two versions and the check’s verdict on each; the separation label derives from these two verdicts. contrast applies the same closure to both outputs, and countSeparations counts the four categories. The reason for the rule: a check’s value lies not in passing but in telling the two versions apart.

Force Unwrapping

In Swift an optional value can be nil. Force unwrapping with an exclamation mark assumes that the value is not nil; if the assumption is wrong, the app crashes on that line. The faulty version converts a text to a number and force unwraps the result. The correct version handles the nil case with guard let.

// faulty and fixed — SOURCE
func total(of text: String) -> Int {
    let value = Int(text)!
    return value
}

func total(of text: String) -> Int {
    guard let value = Int(text) else { return 0 }
    return value
}

The text “12” the user enters is converted to twelve in both versions. When the input is “twelve”, the faulty version crashes and the correct version returns zero. If the test never tried this input, both versions pass; the fault shows up in production, on a user’s unexpected input.

Format Check and Rule Check

The model below sets up the duties of the two tools on source text; it is not a real tool itself. The source is kept as an array of lines. The model does not run the code; it only scans the text, just like a static analysis tool.

formatViolations is the format check: it counts tabs, trailing spaces at the end of a line and indentation that is not a multiple of four spaces. forceUnwraps is the rule check: it counts exclamation marks that come right after a name, ) or ] and are not followed by =. The not-equal comparison and a leading mark that negates a value are not force unwraps and are not counted. When allowLiterals is on, force unwrapping a call whose only argument is a literal text is allowed.

// Lint.swift — format check, force unwrap rule, source lines
func formatViolations(in lines: [String]) -> Int {
    var count = 0
    for line in lines {
        let indent = line.prefix(while: { $0 == " " }).count
        if line.contains("\t") || line.last == " " || indent % 4 != 0 {
            count += 1
        }
    }
    return count
}

func isLiteralCall(_ chars: [Character], closeIndex: Int) -> Bool {
    var depth = 0
    var open = closeIndex
    while open >= 0 {
        if chars[open] == ")" { depth += 1 }
        if chars[open] == "(" { depth -= 1 }
        if depth == 0 { break }
        open -= 1
    }
    guard open >= 0 else { return false }
    let inner = chars[(open + 1)..<closeIndex]
    let afterLabel = inner.firstIndex(of: ":").map { inner[($0 + 1)...] } ?? inner[...]
    let argument = afterLabel.drop(while: { $0 == " " })
    return argument.first == "\"" && argument.last == "\""
}

func forceUnwraps(in lines: [String], allowLiterals: Bool) -> Int {
    var count = 0
    for line in lines {
        let chars = Array(line)
        for index in chars.indices where chars[index] == "!" && index > 0 {
            let previous = chars[index - 1]
            let next: Character = index + 1 < chars.count ? chars[index + 1] : " "
            let postfix = previous.isLetter || previous.isNumber || previous == "_" || previous == ")" || previous == "]"
            if !postfix || next == "=" { continue }
            if allowLiterals && previous == ")" && isLiteralCall(chars, closeIndex: index - 1) { continue }
            count += 1
        }
    }
    return count
}

let correctLines = [
    "func total(of text: String) -> Int {",
    "    guard let value = Int(text) else { return 0 }",
    "    return value",
    "}"
]

let faultyLines = [
    "func total(of text: String) -> Int {",
    "    let value = Int(text)!",
    "    return value",
    "}"
]

let correctLiteralLines = [
    "func home() -> URL {",
    "    return URL(string: \"https://example.com\")!",
    "}"
]

let faultyLiteralLines = [
    "func link(_ input: String) -> URL {",
    "    return URL(string: input)!",
    "}"
]

There are four sources. correctLines and faultyLines are written in the same format; only the second line of the second one force unwraps. correctLiteralLines force unwraps a constant address: this address is known and valid at compile time, so there is no chance of a crash. faultyLiteralLines force unwraps a text that comes from the input and crashes on an invalid address.

The Format Is Blind, the Rule Separates

The first measurement applies the two checks to the two versions of the total function.

// main.swift — format check and force unwrap rule
let format = contrast("format-check", correct: formatViolations(in: correctLines), faulty: formatViolations(in: faultyLines), check: { $0 == 0 }).describe()
let rule = contrast("force-unwrap-rule", correct: forceUnwraps(in: correctLines, allowLiterals: false), faulty: forceUnwraps(in: faultyLines, allowLiterals: false), check: { $0 == 0 }).describe()

countSeparations([format, rule])
format-check | correct=0/pass faulty=0/pass -> blind
force-unwrap-rule | correct=0/pass faulty=1/fail -> separating
separating: 1
blind: 1
noisy: 0
inverted: 0

format-check is blind: both versions are indented properly and have no trailing spaces. The format does not say that the code may crash. force-unwrap-rule is separating: the correct version has no exclamation mark, the faulty version has one.

This does not mean that the formatter is unnecessary; it answers a different question. The format ensures that the code is readable and that no unnecessary differences appear during review. Finding the fault is the rule check’s job. One does not replace the other; the sentence “the format check is clean” says nothing about the code being correct.

TP1. The model scans the lines as text and does not tell an exclamation mark inside a string apart. A real tool works on a syntax tree and eliminates these false matches.

TP2. The model counts only a single rule. A real tool runs dozens of rules, and each rule’s separation must be measured separately.

The Bounding Measurement

A rule can find the fault and still be a bad check: if it counts safe use as a fault too. Force unwrapping a constant address does not crash; if the rule flags this as well, the team learns to ignore the warnings.

The measurement below applies the strict rule and the rule with a literal text exemption to the correct version that force unwraps a constant address and to the faulty version that force unwraps a text from the input.

// main.swift — false positive and exemption
let strict = contrast("strict-rule", correct: forceUnwraps(in: correctLiteralLines, allowLiterals: false), faulty: forceUnwraps(in: faultyLiteralLines, allowLiterals: false), check: { $0 == 0 }).describe()
let exempt = contrast("exempt-rule", correct: forceUnwraps(in: correctLiteralLines, allowLiterals: true), faulty: forceUnwraps(in: faultyLiteralLines, allowLiterals: true), check: { $0 == 0 }).describe()

countSeparations([strict, exempt])
strict-rule | correct=1/fail faulty=1/fail -> noisy
exempt-rule | correct=0/pass faulty=1/fail -> separating
separating: 1
blind: 0
noisy: 1
inverted: 0

strict-rule is noisy: the correct version is flagged too, because the rule does not tell a constant address from a text that comes from the input. Both versions fail and the check says nothing. exempt-rule is separating: a call whose argument is a literal text is allowed, and a text from the input is flagged.

Defining an exemption is a decision too. An exemption that is too wide hides the fault again; one that is too narrow keeps the false positive. Here the exemption depends on a single condition, that the argument is a literal text, and this condition is written in the rule itself; comments added above a line saying “skip this line” are each a separate loss of checking.

TP3. The exemption covers only a single call form. That a literal text is really valid must be verified separately, outside the rule.

TP4. The model does not run the crash. That the faulty version crashes on input is known from the structure of the source text, not measured.

Common Mistakes

  • Reading a clean format check as the code being correct.
  • Turning rules on and not examining their results; silently turning off a rule that gives false positives.
  • Flagging all force unwraps and not separating the safe ones; the team gets used to ignoring the warning.
  • Silencing every warning with a line comment. This disables the rule on that line.
  • Putting static analysis in place of a test. A rule finds only the pattern it describes; it does not test behavior.

Summary

  • A formatter arranges spaces and indentation; a static analysis rule looks for a specific structure. They answer different questions.
  • Force unwrapping compiles and runs in tests, but crashes the app on unexpected input.
  • The format check passed on both versions and stayed blind; the rule check passed the correct version and failed the faulty one, and separated.
  • A strict rule that also flags safe use failed on both versions and became noisy; the literal text exemption brought the separation back.
  • A rule’s value lies in finding the fault and in not interfering with safe use.
  • This lesson’s separation: blind.

Next Step

After the test and performance checks, the code is packaged to reach users. The next topic, Distribution, explains with its first lesson, Signing and Provisioning Profiles, how the app’s identity is proven.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close