We get feedback from the compiler. We get feedback from Test-Driven Development. But what sources of feedback lie in between?
This is where linters come in. A linter goes beyond “Does the code compile?” A linter answers questions like, “Is the code idiomatic? Is it stylistically clean? Are there any red flags?”
How do we get computer-assisted feedback?
We want the ability to fearlessly change code. The antidote for that fear is feedback. That's why I practice Test-Driven Development. TDD gives me fast feedback about whether the behaviors of my components match the desired business rules.
At the other end, compilers don't understand business rules. But they do understand programming languages. The most basic level of compiler errors tell us whether our code is language compliant. But Xcode also offers:
- shallow (faster) Analyzer warnings
- deep (slower) Analyzer warnings
You can specify what level of Analyzer warnings to use for the “Build” action, as opposed to the ”Analyze” action. My practice is to turn up Xcode warnings as far as I can stand them.
So far, all of this analysis is what we might call ”strong rules”: they apply to any codebase. But beyond these strong rules, there are softer rules. This is where linters come in.
The softer rules of linters
Soft rules are discovered by the larger community. These rules often express idioms. Such idioms may make sense for one team (or codebase) but not another.
I’ve been using linters since my C and C++ days. Those old linters issued a lot of messages! At the time, much of it seemed like noise. Either they were inherently noisy, or my code was horrible. (Maybe both!) But I don’t remember any way to configure linters with what to report and what to ignore.
Things are a lot better today! Modern linters let you specify which rules you do and don’t want for a particular project.
For Objective-C, I use OCLint. It offers rules like:
- Dead code
- Inverted logic
- …and many more
…Well, I used to use OCLint. I largely stopped because AppCode’s code inspections give me good feedback with zero set-up. AppCode tells me about:
- Typos in camel-cased identifiers
- Unused code
- Unused imports
For Swift, the go-to linter is SwiftLint. It offers rules like:
- Cyclomatic complexity: a function with too many paths is hard to reason about.
- Private unit test: tests marked private will silently fail to run, because they’re hidden from the test runner.
- Trailing semicolon: harmless but noisy. Easily happens if you switch back-and-forth between Swift and Objective-C.
What about AppCode “code inspections” for Swift? There’s actually a nice SwiftLint plugin for AppCode. With this plugin, I can instantly see SwiftLint messages within AppCode, before compiling.
Of course, AppCode still reports typos in camel-cased identifiers, since that’s independent of language.
SwiftLint is updated regularly with new rules. Some rules are enabled by default. Others are “opt-in” and won’t take effect unless you explicitly enable them.
To see the current list of rules, type
In the spirit of turn it up to 11, I want to enable as many opt-in rules as I can stand. (Click here to see my SwiftLint configuration for the MarvelBrowser project.) Let me point out the few opt-in rules I don’t enable:
- explicit_top_level_acl: There’s a chance that portions of your app may be extracted into a framework. But even if that happens, it will likely be bits and pieces, not the entire app. I’d rather not waste time on annotations that won’t ever matter.
- explicit_type_interface: I prefer to use type inference as much as possible, so I keep this off. By the way, AppCode has an option to show inferred types as if you had written them explicitly. This gives you the best of both worlds. You can find this option by searching for ”Show Swift type hints”.
- private_outlet: Private members can’t be accessed by unit tests. I want unit tests that exercise my outlets. See Testability, Information Hiding, and the Class Trying to Get Out
- single_test_class: Sometimes, I write multiple test suites against a single component. When a main test suite replaces a component’s defaults, I have a smaller “defaults” test suite to confirm those defaults are in place.
Linters are like warnings on steroids. Experiment with the opt-in rules to see which ones make sense for your projects. When you update a linter, don’t forget to check for new rules you can use.
Test-Driven Development helps us write code that satisfies business rules. Linters (and static analysis in general) help us write code that is cleaner. It’s win-win.
What static analysis tools do you use? And how do you like to use them? Please share in the comments below.
TDD helps me write code that’s correct. SwiftLint helps me write code that’s cleaner.