Hello there, my sweet fellow developers,
I have a story to tell you. Once upon a time I was bored. At that time I was playing around with my classes as usual, writing the tests for them in Kiwi. And I stumbled upon an interesting problem, I really wanted mocks for blocks to remove unnecessary boilerplate. Sadly, noone has opensourced that. So I thought, why don’t I write it? A pull request with some really interesting functionality wouldn’t hurt me or the community. So, I started implementing the code and found loads of strange things on my journey of refactoring and digging the runtime lib. Sadly, the story didn’t end well, as when I submitted the pull-request, the maintainers decided to stop adding any new features with large functionality. And I was like FFFFFFUUUUUUUUUUUUUUUUU…., because I started using that Kiwi feature in my projects prior to the pull-request being accepted, which means that I had to maintain the parallel branch. The story of my life. Oh well…
Still, during that journey I found one interesting thing scattered across the code:
1 2 3 4 5 6 7 8 9 10 |
#if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG @try { #endif // #if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG ... #if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG } @catch(NSException *exception) { KWSetExceptionFromAcrossInvocationBoundary(exception); } #endif // #if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG |
If you take a look at it, you’ll see, that it’s scattered all over the code, but it has a clear pattern. What this means to us is, that it should have been deduplicated. Why? because from what it looks like, this hack is not actual any more, but removing it from the codebase is really difficult, as the code is not isolated by any means. And, from what I gathered, that hack is unneeded any more (I don’t know the reason of its introduction, but tests don’t fail with it disabled, at least it seems like it, as I didn’t research deeply).
You shouldn’t consider this as a rant against Kiwi, as it’s not. Kiwi awesome and its developers and maintainers built a tool I used on the day to day basis for several years and am using it even now, when writing ObjC code.
After reading through the code I decided, that I don’t want to stumble upon such issues myself, so I added a guideline to make all the macro specific code isolated. If we apply that guideline to the bad code I wrote specifically for you:
1 2 3 4 5 6 7 |
var intValue = 1 intValue += 1 #if os(iOS) intValue *= 2 #endif print(String(intValue)) |
This is not the only specific thing in swift out there. There loads of them, and some aren’t even marked with #. So, the better course of actions for such a code is to at least isolate it into separate function.
1 2 3 4 5 6 7 8 9 10 11 |
func osSpecificProcess(_ value: inout Int) { #if os(iOS) value *= 2 #endif } intValue = 1 intValue += 1 osSpecificProcess(&intValue) print(String(intValue)) |
However, that’s not the best idea as well. Why? We have different specific operations throughout our code, which leads us to the same problem of the #if os(iOS)
, which is both duplication and is difficult to refactor. So, the better approach is to do it the same way I worked with injecting the closures and isolating such code:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func iOSSpecific(_ f: () -> ()) { #if os(iOS) f() #endif } intValue = 1 intValue += 1 iOSSpecific { intValue *= 2 } print(String(intValue)) |
That’s all, folks. Have a great day and stay DRY, no matter, where you are.