Hacker News new | ask | show | jobs
by guggle 1756 days ago
"Which program is easier to read?"

For me it was the second. Am I the only one ?

5 comments

The first one reads like somebody just found out about functional programming and functors and tried to "improve" a straightforward bunch of if statements. I can read the second program without having read the plain English description, but I definitely would prefer to have the comment for the first one. I think even in languages like Haskell I would prefer (just sometimes) to read just a straight if-elseif-elseif-else.

The whole thing about the way it's written with throwing/catching is a red herring anyway, you should just replace those with a different choice of if's. If you're feeling super adventurous, you can instead replace them with goto's, which is kinda funny; it would actually simplify the code, how often do you see that?

I think part of the problem is that Typescript doesn't have support for error-as-value baked into the language and pervasive in the ecosystem, so adopting that style isn't as ergonomic as it would be in a language that does. The equivalent in Rust would be far more clear and concise due to the ? operator and Result-types being ubiquitous.
The second by far, especially if you just remove the else statements and let program flow continue naturally.

There are very good reasons to prefer Result over exceptions, but this example is not one.

Agree and it could be easier to read in my opinion if you deal with the exceptions first. For example.

    let v = Number.parseInt("a3", 10);
    try {
        if (Number.isNaN(v)) {
            throw new Error("NaN");
        } else if (v > 3) {
            throw new Error("gt 3");
        }
        v += 1;
    } catch (error) {
        v = 3;
    }
    v += 1;

Or writing a "guard function" that throws ...

    function throwIfNaNorGt3(v) {
        if (Number.isNaN(v)) {
            throw new Error("NaN");
        } else if (v > 3) {
            throw new Error("gt 3");
        }
    }

    let v = Number.parseInt("a3", 10);
    try {
        throwIfNaNorGt3(v);
        v += 1;
    } catch (error) {
        v = 3;
    }
    v += 1;
Tbh, to me the first one is easier to read. There's less jumping.

But both are terrible. It should just be a bunch of if (...) { ... } else if (...) { ... } else { ...} etc. with no mutation of variables (what are all those v += 1 for?).

Those v += 1 implement the exact specification given above:

    1. Parse an integer N from a string.
    2. If N is NaN, fail with an error. Otherwise, increment N by 1.
    3. If N is > 3, fail with an error. Otherwise, increment N by 1.
    4. If steps 1-3 failed, set N to 3.
    5. Increment N by 1.
Those are quite strange requirements, and the resulting second code looks strange too, but... it faithfully and obviously correctly represents the given specification.
The example is strange and contrived. I couldn't torture the logic enough to demonstrate how readability breaks down with exceptions. Reading through the comments, I realise that even if the code is unreadable with exceptions, it can be rewritten to be readable. I updated the article to highlight not only readability, but also compositionality. I also tried to draw parallels with Promises (which are also monadic). I tried to target the article to intermediate programmers that may not already know most of the concerns raised in the comments, and I surely can't cover enough space on error handling without writing a book.

Thank you for the comments. I've learned more from reading the comments.

It's the kinda assignment where you should look at a higher level - what does it do? What is N used for? What are the possible inputs?

I mean if you start with a set of tests instead of pseudocode written down in text you could probably write something smarter.

Second one is way easier to read, even though it's verbose on purpose.