On Readability, Thought Process, and Coding Fluency

I couldn’t come up with a good title,
but what I really wanted to express is how I’ve recently come to appreciate
how Postfix Completion (or Postfix Templates) improves coding fluency.
(All mentions of “readability”, “thought process”, “coding order”, and “coding fluency” in this post are based purely on personal experience and subjective opinion.)

In recent years, due to work, I’ve been switching between multiple programming languages.
Lately, I’ve been working in the Python ecosystem — it offers rich libraries, a very developer-friendly syntax, and excellent readability.

But no matter the language, I always felt a slight friction in the development process.
Eventually, I realized it might be caused by a mismatch between coding order and thought process. Here’s what I mean:

When we think while coding, our order usually goes like this:

  1. Identify the variable (possibly the result of a previous step)
  2. Decide what to do with it (function/operator)
  3. Get the output

The thought process feels like this:
a + b = c; c - d = e; e * f = g; g / h = answer
It flows from left to right, with input on the left and output on the right, all in the same direction.

But our actual code often looks like this:

c = a + b  
e = c - d  
g = e * f  
answer = g / h

Now our eyes jump first to the right at a + b, then to the left at c, then to the right and down to c - d,
forming a kind of reverse-Z or zigzag scanning pattern.
Because output variables are always aligned at the start of the line, “readability” is improved — but “coding fluency” suffers. Why?

For example, calling an async function like:
c = await add(a, b)
It looks clear, but the “coding order” doesn’t match the “thought process”.

The natural thought process might go:
Think of a → use the function add → add parameter b → decide to await → finally assign result to c.
But the coding order is left to right: you first write the result c, then =, then await, then the function add, and only at the end, the parameters a and b.

If the right-hand side await add(a, b) needs to be changed later, you’ve already named the output c — so you have to move your cursor back to rename it.
And if you want to treat add(a, b) as a task instead of awaiting it, the await — which you already wrote early on — needs to be deleted, again requiring cursor movement.
(By the way, I used to like Rust’s postfix .await — it matches thought process and doesn’t interrupt a chained call,
but the most important part, await, ends up hidden in the middle of the chain, which hurts “readability”.)

This reminds me of C#'s LINQ query syntax. It’s similar to SQL, but you write from first, then where, and finally select.
This order matches the developer’s thought process: input source → filter conditions → output result.
SQL, on the other hand, starts with SELECT, when the developer doesn’t yet know what tables to access or what data to filter.

Eventually, I discovered that IDEs support postfix completion for await,and I felt it really helped align coding order with thought process.

Take the async function add as an example. Here’s a simulated input scenario:

a|
# Cursor is right after `a`

a.add|
# Type `.add` and press Tab
# [1]

add(a, |)
# IDE converts to function `add` automatically, with cursor at the second parameter

add(a, b)|
# Type second parameter `b`, press End to move cursor to the end

add(a, b).await|
# Type `.await` and press Tab

await add(a, b)|
# IDE adds `await` automatically

await add(a, b).=|
# Type `.=` and press Tab

| = await add(a, b)
# IDE moves cursor to the beginning and inserts `=`

c = await add(a, b)
# Type output variable `c` — done
[1] .add here is a custom snippet for demonstration purposes. Ideally, IDEs should suggest suitable functions for the variable automatically.

Another example: I want to get the number of items in member_list and assign the result. member_list looks like this:

member_list = ['Frank', 'Miki', 'Jack']

Simulated input:

member_list|
# Cursor is right after `member_list`

member_list.len|
# Type `.len` and press Tab

len(member_list)|
# Converts to function call, cursor at the end

len(member_list).=|
# Type `.=` and press Tab

| = len(member_list)
# Cursor moves to the beginning and inserts `=`

member_count = len(member_list)
# Type variable `member_count` — done

In this example, len(member_list) looks like “length of member_list”,
making the key idea — “get the length” — easy to understand.
But personally, I often first think of what I want the length of (i.e., member_list),
then apply the function len(), and finally name the result.

If I wanted to change the function from len to filter, I could — because I haven’t written the output variable yet.
But in the traditional coding style, I would’ve already written member_count, and then have to refactor it to something like filtered_members.

Defining functions has the same issue:
The coding order is asyncdef → function name.
Easy to read, but unfriendly for typing.
We usually first think of the function’s purpose (i.e., get_member_list), then use def to declare it, and finally decide whether it needs to be async.

In fact, the async modifier should ideally be inferred by the IDE.
If a sync function contains an await inside, the IDE should automatically add the async modifier,
and propagate this change upward: any function calling it should also add async/await — a kind of “automatic function coloring.”

To sum up:
Since I started using Postfix Completion, coding has felt much more fluent. I highly recommend giving it a try.

1 Like

I think c = a + b matches my thought process better than a + b = c would. I think of c first. It’s where I’m going. I don’t aimlessly compute a + b and then decide to make that c. The reason I do a + b is because I want c. Making up my mind about what I want comes first.

4 Likes

You didn’t say, but this appears to be an IntelliJ feature?

It’s interesting, but ultimately

  • I’m not going to switch editors just to try it
  • doesn’t look like it’s for me anyway

I’ll just quickly note that I almost never think or even literally code in that order.

My order for complex problems is:

  • what result do I want?
  • name it (often a function or class, sometimes just a variable)
  • is there a higher order abstraction here?
  • keep building abstractions and interfaces until the problem is simple

For example, I recently worked on an application which computes statistics for a user. The rendering of results had to be tweaked for a new feature in a special way based on the user’s exact request.
My process started with

Here, in the output component, I wish I had “InitiatingRequestInfo”, that would make this really easy.

and then I built from there.


I’m not going to “yuck” someone else’s “yum” though; I’m glad the tooling fits your needs and style!

1 Like

For me the thought process is often

answer -> starting variables -> operation 1 -> operation 2 (plus variables that decide how to do operation 2) -> ...

ie:

answer = (((a+b) - d) * f) / g

(As I’m writing this, repeatedly having to go back to the start of the line to add brackets is characteristic of my personal pet-peeves with typing Python.)

so for example if the answer is a length, len is the last thing I think of not the first.

So for me it’s useful to know that postfix completion exists. But it’s not enough to switch to Pycharm. And there doesn’t seem to be a particularly mainstream postfix implementation for VS Code?

I’m not sure what would cause that reading pattern. I read it as ‘c equals a plus b,’ ‘e equals c minus d,’ and so on. If I were explaining the problem aloud to someone else, I’d say something like, ‘Let c be a plus b,’ and so forth.

Oh, I enjoy discussions like this.
They always offer different perspectives
and help me think from various angles.

Indeed, I should break down the components starting from the intended functionality.
The thought process should look something like this:

anwser  
   ▲  
   └ g  
     ▲  
     └ e  
       ▲  
       └ c  
         ▲  
         └ a  
         ▲  
         └ b  
       ▲  
       └ d  
     ▲  
     └ f  
   ▲  
   └ h  

But actually, I’m not trying to recommend any specific IDE.
Rather, I’m proposing a way to make the code order smoother—
and this approach is supported to varying degrees across many major IDEs.

VSCode’s implementation of postfix completion relies on extensions.
So far, I haven’t found any extension that handles the following use case satisfactorily,
but with some extension logic, it should be achievable:

While it doesn’t solve the issue, the alt + shift + ←→ shortcut in VSCode (scope based selection) helps somewhat:
misery

2 Likes

You might be interested in voicing your support for a pipeline syntax proposed in the ongoing discussion, where with my proposed syntax plus a later revision to position a piped object as the first argument by default, your throught process above can be better expressed as:

from operator import *

a + b |> sub(d) |> mul(f) |> div(h) as answer # or a |> add(b) |> sub(d) ...

or:

a + b |> _ - d |> _ * f |> _ / h as answer

Like I mentioned in the post, the proposed syntax is all about giving people the option to write code that better fits their mental model.

2 Likes