Please read what are you coding

Today I want to talk about code readability and how it can be achieved. It’s a hard topic. There are a lot of articles about this. But today our main target will be – better readability for faster reviewing.

Filler words

I noticed that everyone tries their bests to make code readable but focuses on other things, making their work ambiguous. A lot of variables/arguments called `handler`, `action`, `object`, `text` etc. I call them filler words. They exist just because programming language requires variables to be named. They don’t mean anything. At the same time they can hold anything you want.

private func makeCardView(book: BookType) -> UIView {
    // redacted

    let stack = UIStackView(axis: .vertical, alignment: .trailing, views: [
        coverView,
        with(Button()) { actionButton in
            actionButton.setImage(UIImage(named: "dots-m")?.template, for: .normal)
            actionButton.matchSize(to: CGSize(width: .m, height: .m))
            actionButton.contentMode = .scaleAspectFit
            actionButton.tintColor = UIColor.textSecondary.resolved(setUserStyle)
            actionButton.setButtonAction(control) { button in
                router().showBookEndActions(book: book, sender: .view(button))
            }
        }
    ])

    // redacted
}

Sample from some working iOS app, just for showing how strange it looks

Let’s talk about function and argument naming here. What if your new function allows executing some kind of existing queries? You problably end up with signature: `func execute(query: Query) -> Result`. Appearatly developers from Apollo iOS (GraphQL client) did the same and now it ended up like this:

public class ApolloClient {
    // ...
    @discardableResult public func fetch<Query: GraphQLQuery>(query: Query,
                                                            cachePolicy: CachePolicy = .default,
                                                            contextIdentifier: UUID? = nil,
                                                            queue: DispatchQueue = .main,
                                                            resultHandler: GraphQLResultHandler<Query.Data>? = nil) -> Cancellable {
    // ...
    }
}

And how it is used?

let query = HeroDetailsQuery(episode: .empire)

client.fetch(query: query) { result in
// ...

Link to sample

It seems everything is correctly written. At first glance. And now read the code again but from reviewer perspective. Now you aren’t using anything like autocompletion or quick-help, which shows documentation for the function. You’ll notice that word “query” is written twice. You usually don’t speak like this, do you? I-I am writing-writing some function-function to retrieve-retrieve a result… Everyone wants computers to be more human-like and creating programming languages using English words was the first step. Now we have personal voice assistants talking almost freely. What stopping us to write code more humanly?

Fixing the code

Let’s remove duplicated appearance of the word `query`. From now I will use more simplicated version of the function.

 public class ApolloClient {
-    func fetch(query: Query, resultHandler: @escaping (Result) -> Void) {
+    func fetch(_ query: Query, resultHandler: @escaping (Result) -> Void) {
         // ...
     }
 }
// ..
let query = HeroDetailsQuery(episode: .empire)

client.fetch(query) { result in
// ...

Better now. But retrieving result still looks unnatural. At this point it is unclear, what purpose of this closure. It may be called when fetching is finished or while it is in progress. This `fetch` method will give us a result. Let’s put this in code. Our goal to have this method usage:

client.fetch(query).andGive { result in

It reads how it’s written: “Client, fetch query and give result in”. To make this possible, we have to remove completion handler from method arguments. Instead of we will return a some type.

public class ResultGiver<Data> {
    var result: Data? {
        didSet { fireIfNeeded() }
    }
    var closure: ((Data) -> Void)? {
        didSet { fireIfNeeded() }
    }

    private func fireIfNeeded() {
        guard let result = result, let closure = closure else { return }
        closure(result)
        self.result = nil
        self.closure = nil
    }

    public func andGive(_ closure: @escaping (Data) -> Void) {
        self.closure = closure
    }
}

public class ApolloClient {
    /// To make it easier to follow, we are creating a decorator instead of rewriting the original method
    func fetch(_ query: Query) -> ResultGiver<Query.Result> {
        let giver = ResultGiver()
        self.fetch(query) { giver.result = $0 }
        return giver
    }

    func fetch(_ query: Query, resultHandler: @escaping (Result) -> Void) {
        // ...
    }
}

Of course some properties should be hidden from the public, probably should be a value type. But for now, we will leave it like this. Now usage will look exactly as we wanted! It is a minor accomplishment for us, but for reviewer it gives a some kind of pleasure while looking at this. Why we are afraid to write code like this?

Swift centric problem

Actually this problem is tied to Swift community. Probably it came from Objective-C, where ability to create DSL was limited. For example, Ruby (especially RSpec) abuses (positively) the ruby syntax to create a beautiful API. If you don’t know, Ruby allows brackets to be omitted. Combined with a creativity of the developer, it can create the most satisfying and self-explanatory code:


RSpec.describe 'User.referralsCount', boot: %i[graphql rom] do
  # redacted

  let(:expected_data) do
    {
      data: {
        viewer: {
          referralsCount: expected_count
        }
      }
    }
  end

  let(:expected_count) { 0 }

  specify 'referralsCount: Integer!' do
    expect(response).to be_json_eql expected_data.to_json
  end

  # redacted

Conclusion

Appearantly, clean code is a code which easy to understand. To make it happen, we have to write it as we speak: naturally, humanly. Try to talk to the computer, because at the end you are talking to a human.