In searching around this morning for some examples of complicated if-then-else-else-else statements on gist (if you have any, please point me to them!), I stumbled across this function for scaling and cropping an image.
I immediately decided to re-write the function in my more personal style, to see what kind of changes I’d introduce and whether any of them could be generalized and discussed as potential book fodder / post fodder.
Before looking at my version, which is here, you may want to whip up your own take on things and then return to see why I made some of the decisions to introduce changes in my take on it.
If you do the math, my version takes about the same number of lines as the original, both are about 38-43 lines of code. Both use Swift 2 code and not Swift 3 code. My code uses more comments, fewer vars, more lets, more conjoined code, and more multi-line calls.
While the original performs a fill-image operation, mine does both fit and fill, using defaulted arguments, which is one of the reasons my function signature occupies 5 lines compared to the original 1-line signature. I find myself more and more breaking signatures down to multiple lines to allow better labeling, defaulting, and documentation.
I’m less worried these days about taking up vertical space as I find that stacked parameter lists are easier to read and understand. Each parameter line may contain an external label, an internal label, a type, and possibly a defaulted value. Swift’s move towards complex parameters deserves the presentation that best exposes these items to their full expression.
Next, I converted the initial if-check to a guard statement. When a function’s code is meant to test initial conditions and immediately leave scope if those conditions are met, it seems to me a guard statement better represents that entry point than “if”.
In calculating the scale factor, I perform a tuple assignment. These factors are semantically parallel, so their assignment is joined and parallelized as well. It’s a fairly Swift-specific approach, and one that should be used sparingly but this fits my parallelism criteria, and I do the same just below when calculating the scaled width and height.
I follow this with a pair of ternary tests, first for choosing whether to use fit or fill, and then choosing the scale factor that applies the fit or fill set by the fitImage Boolean parameter. I think it’s pretty straight forward. A fit operation constrains itself to the smaller of the two scales and a fill to the larger.
In creating the drawing destination, I decided to break out my CGPoint construction to three lines. Again I felt this read better and was easier to follow than placing both the x and y parameters on a single line, which grew quite long. The top-down alignment draws attention to the parallels between x and y and width and height used in the two parameters.
I took some liberties in the UIGraphics drawing section, deciding to use a do clause for the actual drawing. In Objective-C, I often use scoping to distinguish the drawing operations from the bracketing begin and end calls. Because of scoping, I had to pull the scaledImage constant declaration out of the do clause but since Swift now lets me defer the assignment to the constant, this worked out pretty nicely.
In the drawing block, I decided to place the background fill on a single line. I initially had this on two lines, but felt it was drawing too much attention away from the actual operation (drawing the scaled image) so merged the two calls using a semicolon.
In contrast, I broke out the scaled image drawing over three lines, to emphasize the use of the calculated destination bounds. I wanted to expand the details of this operation while minimizing the background fill.
There’s some sample tests at the bottom of my version if you want to throw this into a playground and test with real data. The placeholder generator website (http://placehold.it) creates simple block images along whatever dimensions you request.
I’m interested in seeing the kinds of rewrites you came up with and what kind of Swift rules they incorporate. Disagree or agree with my edits? Please let me know. My style choices are still evolving.