ABSTRACT
This is a different post from the previous ones, but I thought it would be a good idea to mention it, as it dovetails very nicely into the work I’ve already been doing with Swift.
I’m pretty uptight about quality. I think it’s something that needs to be designed in from the start; not applied like spackling after the fact.
I also believe that it’s important to develop the habit of quality, as opposed to the discipline or process of quality.
One of my favorite quotes is part of a mis-translation of a treatise given by a guy that wore a bedsheet:
“We are what we repeatedly do. Excellence, then, is not an act but a habit.”
– Aristotle (Sort of)
Software is a fluid, constantly-changing environment, and things that nail us down (I call them “concrete galoshes”) are not an especially good idea.
Discipline and process can easily become concrete galoshes. They are brittle and inflexible.
Habit is a different matter altogether. We just get used to doing something, and it becomes smooth and automatic. If it needs to change, then we simply start applying a new “discipline,” until that becomes habit.
SwiftLint helps me to develop the habit of correct Swift coding.
I have a few quality programming habits, like turning on all warnings, and flagging warnings as errors, and planning for testing from the start (I’m not actually a fan of “pure” TDD, but they have the right idea -maybe I’ll blog about why I feel that way, someday).
By the way, this guy is awesome. We took an iOS class together in 2012, and I’ve been more than impressed by his enthusiasm and intelligence since.
WHAT IS “SWIFTLINT”?
SwiftLint is a “code checker” that was written by the excellent programmers at Realm, who are in the business of creating a synchronized live object platform. I’m assuming the platform works well (I’ve never used it, myself; but might in the future). This is partly because I’m familiar with two pretty damn awesome open-source products that Realm has shared:
- Jazzy is a really nice code documentation generator that I like a lot more than Doxygen. I may discuss it in another post. I used it to generate the BMLTiOSLib documentation.
- And the topic of this post: SwiftLint.
Both integrate with the Apple variant of the LLVM Back-End PostCompiler.
Because of this, they can both “peer into” your Xcode project, as opposed to simply scanning source code. The compiler knows a lot about what your code is about “beneath the sheets,” so these projects can make some very intelligent decisions.
USING SWIFTLINT
I use the Cocoapods version of SwiftLint. This acts on a “per-project” basis. I like it, because it completely integrates with the build, and is easy to write a generic script step for. It’s also portable, and I don’t have to depend on SwiftLint being installed on the system using the code (that’s a big problem with the Homebrew-installed version. It is easy to use -as long as you have used Homebrew to install a global instance of SwiftLint. I’d rather not leave that to chance).
To use it, I simply add pod 'SwiftLint'
to my podfile. I install the pod, and it will always be in a predictable place.
I include SwiftLint inside each of my Swift projects, and I execute it at build time, as part of my Build Phases. I add a “Run Script” step, rename it to “SwiftLint,” and paste the following code into it:
"${PROJECT_DIR}/Pods/SwiftLint/swiftlint"
I like to place the “SwiftLint” step before my “Compile Sources” step, so I get the LINT output quickly (It’s quite speedy).
This means that whenever I do a build, before I actually compile anything, the code is vetted by SwiftLint, and enough errors can break the build (which is exactly what I want).
SwiftLint is BRUTAL
That’s exactly what I want. The job of SwiftLint is to keep me honest and clean. It helps a lot (although I still need even more help).
SwiftLint looks through my project files, and outputs warnings and errors that tell me when I’ve made any of a number of mistakes.
A Quick Example
I took a bit of code from my X-Timer project, and deliberately broke it. The code is for a static calculated property.
In the following example, I’ve inserted a number of SwiftLint mistakes. The app will build just fine, with no complaints:
/* ################################################################## */ /** This returns one default configuration timer "tuple" object. */ static var defaultTimer:TimerSettingTuple { get { let ret: TimerSettingTuple = TimerSettingTuple(); return ret } }
No Compile Errors, Right? Everything’s Golden! W00t!
As I said, the compiler has no issue with this. As far as the Swift compiler is concerned, I’m an elite programmer.
Not So Fast, Skippy.
However, SwiftLint isn’t that impressed by my mad Swift skillz:
I have a number of issues with my code. Let’s walk through them:
Colon Problem
Thankfully, this doesn’t require a colonoscopy to fix. The problem is thus:
static var defaultTimer:TimerSettingTuple
The colon is “too tight.” It needs to be left-aligned, with a space between the colon (which should be against the left term, with no space), and the right term, like so:
static var defaultTimer: TimerSettingTuple
We just hit the space bar, and that problem is fixed.
/* ################################################################## */ /** This returns one default configuration timer "tuple" object. */ static var defaultTimer: TimerSettingTuple { get { let ret: TimerSettingTuple = TimerSettingTuple(); return ret } }
Bracing Issue
Next, we have an issue with how we indent/brace the var:
static var defaultTimer: TimerSettingTuple
{
Swift programming doesn’t have an official “Style Guide” yet, but we’re rapidly developing it as time goes on. In the meantime, we have SwiftLint.
SwiftLint likes BSD KNF Indenting.
For years, I used Whitesmiths Style Indenting, but I’ve thrown in the towel, and now use KNF for everything.
The problem here, is that this indent is more an Allman-Style Indent than a Whitesmiths or KNF-Style.
We fix that by tweaking the indent properly:
static var defaultTimer: TimerSettingTuple {
You Just Don’t Get It
The next issue is one of “implicit getter.”
Note that I have specified the get { ... }
keyword and context. This is not necessary, if we only have a getter.
/* ################################################################## */ /** This returns one default configuration timer "tuple" object. */ static var defaultTimer: TimerSettingTuple { get { let ret: TimerSettingTuple = TimerSettingTuple(); return ret } }
We fix that by removing the get { ... }
keyword and context:
/* ################################################################## */ /** This returns one default configuration timer "tuple" object. */ static var defaultTimer: TimerSettingTuple { let ret: TimerSettingTuple = TimerSettingTuple(); return ret }
Unnecessary Semicolon
Swift doesn’t require semicolons unless you are stuffing a single line with multiple steps (a bad idea). However, I am constantly switching languages with PHP, which absolutely loses it if you forget a semicolon. As a result, I often screw up, and add an unnecessary semicolon:
let ret: TimerSettingTuple = TimerSettingTuple();
We get rid of that, and SwiftLint finally stops complaining.
After all that, the var should look like this:
/* ################################################################## */ /** This returns one default configuration timer "tuple" object. */ static var defaultTimer: TimerSettingTuple { let ret: TimerSettingTuple = TimerSettingTuple() return ret }
Plenty More Where That Came From
SwiftLint has a whole bunch of built-in rules, and I suspect the number will grow, over time.
You can modify how these rules work via an invisible file called “.swiftlint.yml”. This is a YAML file that you put in the directory where you’ll be running SwiftLint.
You can put in rules that SwiftLint will read, and use to modify its behavior. I tend to have a somewhet more relaxed approach than many, so I skip a few rules, and loosen a couple more. Here’s the YAML file for the X-Timer project:
disabled_rules: - identifier_name - nesting - function_parameter_count - trailing_whitespace - type_name - function_body_length opt_in_rules: - control_statement - empty_count - trailing_newline - colon - comma excluded: - Pods - Project/R.generated.swift file_length: - 1500 # warning - 3000 # error type_body_length: - 400 # warning - 500 # error line_length: - 400 # warning - 500 # error cyclomatic_complexity: - 13 # warning - 15 # error
There’s people who get religious about these rules. You want to throw poo at me and call me bad names?
Knock yourselves out. It probably won’t affect me much.
For example, Most folks like to keep Cyclomatic Complexity to a factor of ten or less.
I generally let it go a bit higher; depending on the application. In many cases, breaking a function with a CC of 13 into three different functions makes the code more complex and slower than keeping the first, more expensive function.
Again, people get religious and foam at the mouth over this stuff. I like CC less than ten, and usually get it, but I sometimes have instances where it’s a better idea to just let the switch
statement have a couple more cases, as opposed to adding yet another callout and stack frame. This usually happens a couple of times per project.
If the CC is REALLY bad, then it’s usually a sign that the function needs to be refactored completely. If it’s a bit high, then I can sometimes do things like rejigger a couple of if { ... }
branches. In some cases, there’s just no way around all the choices, and I do need to break up the function.
In a perfect world, it is sometimes possible to refactor the function in such a way as to reduce the choices (like creating classes for each case, and a factory to generate them), but this can often be a major (read: “risky”) project, and not one that should be done, just so you can feel smug about your code.
Failing that, I may tweak the cyclomatic_complexity
rule in the YAML file to let me “get away with it.” If I do that, I will often drop the CC rule occasionally to see where my expensive functions are, and work on ones that need it, and can be fixed. I’m not lazy; just practical. I deliberately break the build on warnings and SwiftLint errors, so I need to take that into account when I deal with this stuff. Sometimes, you just have to hold your nose and proceed, but that should be quite rare. I don’t like code that I need to “hold my nose” over. It just feels…bad.
AAAARGH!!! THE HORROR! THE HORROR!
Remember what I said earlier about “habit”? It will become very important as you move forward.
That said, the first time you run SwiftLint on your code, you are likely to feel just a wee bit overwhelmed:
Yup. That’s what my X-Timer project looked like before I started to clean it up. I had to address each and every one of those issues, but I did it all within a couple of hours. SwiftLint is pretty good at telling you exactly what’s wrong.
It’s likely that you’ll be able to clean up quite a few of the issues with the judicious use of search-and-replace (Remember to back up –A LOT. Backups are gooood).
It’s worth it to go through the files and clean the stuff up. Don’t give in to temptation and turn off all the rules in the YAML file. Fix as many as you can.
What I do, is fix everything except the cyclomatic complexity issues, then I look at each CC issue, and see whether or not I can fix it, or if an exception needs to be made (HINT: I rarely make an exception, and when I do, it isn’t by much).
Once you have the project cleaned up, remember what needed cleaning, and simply develop the habit of coding that way from now on.
UPDATE
As time has progressed, I’ve learned a bit more about “idiomatic Swift,” and would probably write that same computed property like this, nowadays:
/* ################################################################## */ /** This returns one default configuration timer "tuple" object. */ static var defaultTimer: TimerSettingTuple { TimerSettingTuple() }
I have also taken to simply checking in the binary implementation of SwiftLint, as opposed to using CocoaPods.