Readit News logoReadit News
jbandela1 · 2 years ago
I think “clever” is more related to unfamiliarity.

There is actually a lot of cleverness going on that people just become familiar with.

Structured programming is actually very clever if you think about it.

Function calls are, when you look at it closely, very clever. It encapsulates how to jump to a function entry point, how to pass on values in registers or in memory, how to adjust stack pointers, and all other sorts of cleverness.

For loops are clever with different parts of the statement controlling and executing different parts of the loop.

Compare that with BASIC

A six year old child can understand:

    10 PRINT “Hello”
    20 GOTO 10
Going on to object oriented programming, dynamic dispatch and v-tables are clever. What you call and where you go to in your program are determined by the dynamic type of an object. This is very far from the simple BASIC GOTO

What difference does looking at it like this make.

First we don’t reject automatically reject new concepts just because they aren’t simple. While function calls are complex, they bring many benefits. In addition, this approach emphasizes the role of education to in taking useful concepts and making them familiar to a broader group of people.

HarHarVeryFunny · 2 years ago
I think there's a difference between "clever" and complex.

You can express a complex algorithm or pattern with simple easy to understand code - complexity doesn't have to manifest itself as unreadable or incomprehensible code.

To me "clever" code is more about they way you are doing something than the complexity of what you are trying to do. Clever is the opposite of straightforward and easy to comprehend without a detailed explanation.

For example you might be "forced" to write clever code as an optimization to calculate something in a non-obvious way, maybe also based on some non-obvious pre-conditions that have been assumed and make this a valid approach.

You don't normally want to write "clever" code - you want to write easy to understand straightforward code, and on the occasion when you feel compelled to a clever implementation, for the sake of future you or your teammates, you better precede it with a block comment prefixed with "here be dragons" and a detailed explanation of what it is doing and why it is doing it in this non-obvious way.

dotnet00 · 2 years ago
I also prefer this perspective. I had an epiphany about this sort of code when I was trying to describe what my code did for a research paper, and in trying to express why I was proud of it, I called it complex, only for my research advisor to point out that complexity was not the point of the work, so calling it complex did not convey what about it made the research interesting.

Although it wasn't his intention, it changed my perspective on the "clever" tricks I liked using, since it made me realize that being clever was not the kind of complexity that mattered. So, nowadays I try to write simple, easy to understand code, leaving the cleverness and complexity to the way the problem is tackled.

jiggawatts · 2 years ago
We've built modern computing on top of encapsulated cleverness.

I don't mind clever code, as long as it's a polished gem set into a nice abstraction hiding away the details.

Databases have clever code in them. Network stacks have clever code. Heap allocators have clever code.

We've built our simple code on top of these.

If you need some clever code, write it in the same style: a self-contained library with clean abstractions and thorough documentation.

Whatever you do, never "weave" clever code through simple business logic!

_heimdall · 2 years ago
I've always considered code to be "clever" when its using something in a way it is rarely used, and usually specifically with something very common like an arithmetic operator.

I've seen some really clever code using arithmetic operators to flip variables in ways that look like magic at first. I've also seen, and used myself, a few similar kinds of tricks in JavaScript especially when working with booleans.

I never really considered code "clever" when its just unreadable or incomprehensible. IMO that's just bad code.

dkarl · 2 years ago
> You don't normally want to write "clever" code - you want to write easy to understand straightforward code

I think what the parent comment is getting at is the relative nature of "clever" and "straightforward." At certain times and in certain programming communities, use of deep inheritance hierarchies was "straightforward," and passing a function as a method parameter was so "exotic" and "clever" that languages didn't directly support it.

bexsella · 2 years ago
I work with embedded DSPs, and there are certainly points where the maths gets incredibly dense and hard to intuitively parse. Your last point has proven true for me many times. Luckily, with the exception one block of code, the maths is detailed plainly above the implementation. So, while there might be an occasion where I might not fully understand what's going on, I can see where the original author of the code was coming from, and can follow the following code accordingly. Indeed, I believe one of the comment blocks does start with "Here be dragons".
intelVISA · 2 years ago
Clever is simple, complexity is The Enemy.
ekzy · 2 years ago
Yes. A relevant quote from Alan Kay in his talk “the power of simplicity”:

> one of the things that's worked the best the last three, or four hundred years, is you get simplicity by finding a slightly more sophisticated building block to build your theories out of. its when you go for a simple building block that anybody understand through common sense, that is when you start screwing yourself right and left, because it just might not be able to ramify through the degrees of freedom and scaling you have to go through, and it's this inability to fix the building blocks that is one of the largest problems that computing has today in large organizations. people just won't do it

https://youtu.be/NdSD07U5uBs

P_I_Staker · 2 years ago
Yes, but people are widely familiar with certain concepts. Hating "clever code" doesn't mean hating any abstraction or good elegant ideas (like function calls).

The thing is that this stuff has already been in circulation for so long, and many of the good ideas have been found. If you tried to come up with new solutions for function calls, people would understandably be skeptical.

psychoslave · 2 years ago
Cleverness is like connecting dots.

It might be an instance that reveal a smooth curve that seems so obviously clear afterward but was unfathomed so far.

Or it can cast a baffling intricated sequence of discrete points each generated at coordinates using the previous one in a well specified but completely ungrabbable way, the whole drawing a scary screaming face that any sane mind will flea away from.

mianos · 2 years ago
It's not this and there is a test:

Judging something assumes at least a minimal level of expertise in both the domain and language. I don't know Ruby, a lot looks obtuse, that's not a sign of clever code. If I see examples like the article in languages I know, it's 'clever code'.

MrJohz · 2 years ago
But even then, there's degrees of knowledge and familiarity within that. For example, in Python, is using the `else` block of a for-loop clever? It's a part of the language that I'm familiar with - in principle it's just domain knowledge. But I've worked with plenty of Python devs who've never seen or used that construct before. For them, it's often black magic, and a classic example of "clever" code.

The point is that everyone judges cleverness based on what they know and are familiar with. If I need to think too hard to understand it, then it's clever code. But everyone has different levels of familiarity and experience, which means that cleverness is always an individual metric.

jawns · 2 years ago
Here's an old joke about the progression from junior to mid-level to senior developer:

Junior dev: My code is simple, straightforward, and easy to understand.

Mid-level dev: My code is clever, innovative, expressive, hyper-optimized, and ingenious.

Senior dev: My code is simple, straightforward, and easy to understand.

In software development, "clever" solutions are like poems. In the best poems, there are usually multiple layers of meaning, nuances and subtleties, some harder to tease out than others. Sometimes you have to sit with a poem for a while before you are able to truly drink it all in. To mid-level engineers, writing this sort of poetic code has an intoxicating appeal. It allows them to flaunt their talents, demonstrate their mastery of the language, and impress their colleagues with their ingenuity.

But more often than not, what is really needed is the code version of ordinary prose: straightforward, with a preference for clarity over succinctness, easy for others to understand, easy to edit, and with fewer surprises and deviations from convention than a poem. With prose, particular the sort of no-nonsense style found in wire news reports and explanatory journalism, the best work is easy for the reader to comprehend and lends itself to being edited. For instance, a skilled copy editor can condense it to fit, if need be.

TheCoelacanth · 2 years ago
I don't think it's accurate, though. Junior devs often write overly complicated code because they don't really understand the problem they're trying to solve. Junior devs write unintentionally complex code, mid-level devs write intentionally complex code, senior devs write simple code.
bluGill · 2 years ago
Sometimes seniour devs write complex code but hide the complexity behind a simple interface.
aidos · 2 years ago
With a slight difference that junior dev tends be proud of the code they’re added while a senior will be proud of the code they’ve removed…
latentsea · 2 years ago
So much this.
yieldcrv · 2 years ago
I just say, bell curve meme
Tao3300 · 2 years ago
m463 · 2 years ago
I've seen junior devs write code carefully, putting time and effort into it. The code was ornate and almost too over-commented.

mid-level devs become more pragmatic, but can skimp on both elegance and simplicity vs complication.

What's interesting is that decent senior dev code sometimes almost looks careless, but really works well in the end. For example, immediately exiting with an error instead of complex error recovery. (the latter would just move the problem around and make finding and fixing the root cause more troublesome)

Another thing would be duplicating code instead of doing some complex re-use with complicated conditionals.

JohnBooty · 2 years ago

    What's interesting is that decent senior dev code 
    sometimes almost looks careless, but really works 
    well in the end.
I absolutely love this description.

In a lot of ways, YAGNI is what really sets senior code apart. When I was a more junior dev, I thought senior code often looked under-engineered, but in hindsight I realized I was over-engineering things at the time.

    Another thing would be duplicating code instead of doing 
    some complex re-use with complicated conditionals.
God, I wish I could go back in time and burn this into my brain.

hu3 · 2 years ago
Another example is some devs chain maps and array filters when a mundane for loop would do.
RangerScience · 2 years ago
Mostly, yeah - although

Good poems give you clear meaning now, and deep meaning later.

I think probably great code is like that, too. It’s clear in how it addresses the immediate needs, but it’s deep in how it sets up addressing later, subtler, or less proximal needs.

Git itself comes to mind. At the basic level, it’s super straightforward: you have diffs, you put them in a row, that’s a branch. Easy. Clear.

Then you want to do something weird, and lo and behold, you can. The “cleverness” - maybe call it the “clever simplicity” - that it’s built out of makes it possible, and “easy”. The deep part of the poem, that you didn’t need to understand in the beginning, starts becoming apparent and meaningful.

dheera · 2 years ago
Reality:

This is mostly because mid-level dev needs to justify their existence in order to not get laid off or PIP and is worried about losing their H1B and having to uproot their entire family in 60 days notice. Hyper-optimized, hard-to-read code that only they understand is one way to increase reliance on them while giving a reason that can be put into a promotion doc. Mid-level jobs are worried about maintaining their job.

Junior dev doesn't care because they can go wherever, they aren't worried about the uprooting, and well-written code is a ticket to a multiple new jobs.

Senior dev doesn't care because they have saved enough money, have permanent status, and if the company doesn't want them they aren't worried about there being better opportunities. They have enough online evidence of their competence and don't need to prove themselves.

strken · 2 years ago
When I was a mid-level dev, the overly complicated code I wrote came about because I read an article on e.g. Ruby metaprogramming, got excited, wondered why we didn't use reflection more, and found a place where I could apply it. Perhaps you thought about job security, but that seems like part of your personal journey and isn't tied to seniority. I was just inexperienced and a touch arrogant, as mid-level engineers must be.

At higher levels, the reason I don't write code like that is because I've been burnt too many times by the new hotness. It is slightly about job security, but only because I fully expect that any crazy shit I fling out today will eventually hit the fan and come back to me, probably at 3am during on-call.

ern · 2 years ago
This is mostly because mid-level dev needs to justify their existence in order to not get laid off or PIP and is worried about losing their H1B and having to uproot their entire family in 60 days notice. Hyper-optimized, hard-to-read code that only they understand is one way to increase reliance on them while giving a reason that can be put into a promotion doc. Mid-level jobs are worried about maintaining their job.

That seems to be a matter of survival overriding ethics and professional pride. I know I came up with some really cringeworthy and complex solutions as a mid-level dev, but I would never have done it deliberately.

If people are put into a situation where they need to purposefully write substandard code to not get deported, it's something that needs to be fixed.

fr4nkr · 2 years ago
I would argue that what constitutes clever code varies a lot by language. There's always a "cleverness" threshold where being able to read or refactor the code becomes harder, but this threshold isn't universal.

Python in particular makes it very easy to be too clever, since its extremely rigid syntax was designed specifically to discourage it, but it ended up giving the user the necessary tools to be clever anyway, and the end result is usually... not pretty.

morkalork · 2 years ago
What would qualify as clever Python? These kind of broad and vague statements make me wonder if I am guilty..
sgarland · 2 years ago
For a simple example, I think the walrus operator (:=) could be considered clever. I like it, and use it, but the fact that you can declare a variable, store a value in it, and then perform actions depending on its value, all in one line, gives me pause.

    if (foo := bar()) is not None:
        baz(foo)
Whereas the traditionally accepted Python method of dealing with this would be EAFP:

    try:
        foo = bar()
        baz(foo)
    except AttributeError:
        # handle exception

lolive · 2 years ago
I currently have a dilemma regarding a XML generation code. Should I write it in pure Python with endless nested loops and DOM manipulations. Or should I use a templating engine that is exactly the DSL [:domain specific language] to express declaratively how to generate the output.

Solution 2 is superbly consise and straight to the point, but I am pretty sure noone will ever climb the learning curve that this additional DSL imposes to any newcomer.

I really wonder which final choice I will make for my production code.

[truth is that refactoring solution 2 is painless, but at the same time debugging solution 2 is tricky]

dijksterhuis · 2 years ago
TL;DR

- OOP obsession is a common python developer phase. it's a dangerous phase.

- maintainable code is not minified code. minified code is minified code.

- stoopid code is often stoopid enough when it satisfies real world / human concerns, not technical concerns.

----

I've done all of these, and I see other people repeating them.

1. hyper optimised and utterly fragile class based inheritance / abstractions. Not optimised in performance. Optimised in terms of minimisation of code. avoid ABCs like the plague. YAGNI so save yourself some heartache and keep it stoopid.

2. especially when ^ includes many static methods that could be standalone functions. a good sign the code can probably refactor to functional + objects/dataclasses (and probably be easier to test as a result). You didn't need it, go back to keeping it stoopid.

3. methods that call another method, that calls another method, that eventually calls one of the static methods. often when I see this, none of these child methods are called by anything else. someone wrote multiple separate methods because apparently we shouldn't write methods with more than 10 LoC. because that's how to write clean code apparently. just put it all in a single method so I don't have open multiple different browser tabs while sitting in the doctor's office responding to an incident on my phone. stop optimising for LoC and make it obviously stoopid.

4. hiding how the code will run away from main entrypoint by adding a `className.run()` method. yeah, cool, your main function has been minified. kudos. But now I have no idea what steps your script will run. I have to go and read something else. make it obviously stoopid to someone reading this for the first time.

5. using names of concepts from other languages. don't call classes an "Interface" or a "Controller" because it sounds better. This isn't Java nor is it Kubernetes. It's python. keep names so stoopid that I can understand when my phone has woken me up at 3 am.

6. functional is usually simpler, until you start turning in a mathematician. you are not a mathematician. and neither is the junior sitting next to you. don't overuse recursion or currying etc. keep it stoopid enough that the junior sitting next to you has a chance of taking over responsibility for it one day without going through a PhD in mathematics.

7. avoid using functionality from the last 3x minor versions of python [0]. slow down and let others catch up first so we can all be stoopid together.

----

caveat: experience will vary wildly between different hoomans regarding what is considered stoopid enough.

[0]: a good exception here is something like case matching. this was pretty big so I would have allowed that, so long as everyone was aware it was a new thing now (I'd have done a post on slack saying -- Oi, go look at this, it's big)

collyw · 2 years ago
Coming from Perl, Python made me write more verbose simple code. But I noticed that in Perl I would do something clever, then a few days later it would come back and bit me in the arse, when I was trying to debug it. Sure Perl saved a few lines of code over Python, but my Python code ended up more reliable. I do miss Perl though.
porphyra · 2 years ago
I also find that, in C++,

    int sum = 0;
    for (int i = 0; i < n; ++i)
        sum += x[i];
    return sum
is a lot easier to understand than

    return std::accumulate(x.begin(), x.end(), 0, [](int a, b) {return a + b;});
Yet, the latter is considered more correct and better, with static analysis like cppcheck telling you to use the latter. It does have many advantages, like no mutable variables lying around, but gee it is annoying to read.

a_t48 · 2 years ago
We now have

       return std::reduce(x.begin(), x.end());
Which is a little cleaner and is even faster (compiler is free to do the additions in any order). https://en.cppreference.com/w/cpp/algorithm/reduce - it looks like even the `accumulate` example can be made simpler with `std::plus`. I prefer the `reduce` option for a number of reasons, but understand why someone might not.

lupire · 2 years ago
That's terrible! The main operation, addition, is completely hidden magic! But the completely trivial calls to .begin() and .end() are explicit!

Why would I want to add by default?

floxy · 2 years ago
>the `accumulate` example can be made simpler with `std::plus`

Accumulate, surprisingly enough, accumulates by default:

    return accumulate(x.begin(), x.end(), 0);

vlovich123 · 2 years ago
> and is even faster (compiler is free to do the additions in any order)

Is that actually true? I'm not even sure how hypothetically removing ordering requirements would help you extract performance, let alone any compilers that could do anything with that today. Unless the standard library were to auto-parallelize the reduction, but I doubt they'd do that because the overhead of starting threads would be quite costly for anything but the absolute largest ranges since C++ doesn't have a thread pool sitting idly for you (not to mention that the docs for the function don't mention any thread safety requirements for the BinaryOp and Init which would be required for any such optimization).

autoexecbat · 2 years ago
I think most people would be thinking "hmm now I need to read what 'reduce' mean in this context"
j1elo · 2 years ago
Look at that link:

  2,4,6) ...
  These overloads participate in overload resolution only if
  std::is_execution_policy_v<std::decay_t<ExecutionPolicy>> is true.
  std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> is true.
std::is_execution_policy_v? std::decay_t? std::remove_cvref_t?

Really, this madness with C++ needs to stop. People who work on newer versions of the language might be very conservative with the language syntax itself, but they clearly decided having carte blanche to adding an infinite amount of stuff to the standard library.

Could someone please force them to stop, somehow? The whole rest of the industry would be thankful.

nine_k · 2 years ago
It's mostly the problem of the language, and not of the approach.

In a more expressive language you can omit the explicit slicing (x.begin(), x.end()) and have the compiler derive the lambda's signature for you, so you'd write something like reduce(x, (a, b) => a + b), or even fold (+) x, with all the same static analysis and efficient compilation guarantees.

knome · 2 years ago
I think it's mostly a fad issue. Normal loops and if statements are just as possible to hit with a static analyzer. But the very fact that they look easy makes a certain kind of programmer see them as beneath them. They want the complex looking code, even if it's functionally equivalent and semantically no more sound. They like the visual noise and complexity of it. It rubs their egos the right way.
sgarland · 2 years ago
That is somewhat annoying, yes. I'm a huge fan of Python's list comprehension, but it's generally accepted to be a normal part of the language, and IMO, more readable:

    print([x for x in range(10) if not x % 2])
    [0, 2, 4, 6, 8]
vs.

    l = []    
    for x in range(10):
        if not x % 2:
            l.append(x)
    print(l)
    [0, 2, 4, 6, 8]

okanat · 2 years ago
Call me crazy, but I find the second example more readable.

The first example is long line and you have to go back and forth to understand it. The second example is a single top down pass.

f33d5173 · 2 years ago
I've seen people complain about wrapping a comprehension over multiple lines, but I can't imagine packing them all in one. In your example, I would spread it out over five lines, so it would be at as long as the imperative case.

While I like python, I think the unusual order that components of a comprehension are written is to its detriment. It would make more sense if they were in the same order in both examples. The python ternary has a similar issue.

glandium · 2 years ago
You chose the one example that doesn't need a list comprehension. print(list(range(0, 10, 2)))
psychoslave · 2 years ago
With ruby 3.4 (prior to that `_1` should be used instead of `it`):

    (0..10).select{it.even?}

floxy · 2 years ago

    x = np.arange(0,10,1)
    print(x[x%2==0])

Xeoncross · 2 years ago

    int sum = 0;
    for (int i = 0; i < n; ++i)
        sum += x[i];
Pretty much looks the same in all C-like languages. I've written that in Java, Go, TypeScript, PHP, etc...

On the other hand, that second 'clever' example always looks different for every stupid language. It's std::accumulate in C++, streams in Java, list comprehension in python, etc...

Clear is better than clever.

floxy · 2 years ago
One man's clever is another man's clear.

https://web.archive.org/web/20180619022832/http://webdocs.cs...

"The instructions corresponding to a conventional language might be expressed something like the following:

Select an apple from the box. If it is good, set it aside in some place reserved for the good apples; if it is not good, discard it. Select a second apple; if it is good put it in the reserved place, and if it is not good discard it. ... Continue in this manner examining each apple in turn until all of the good apples have been selected. In summary, then, examine the apples one at a time starting with the first one we pick up and finishing with the last one in the box until we have selected and set aside all of the good apples. On the other hand the instructions corresponding to an array language could be stated simply as “Select all of the good apples from the box.” Of course, the apples would still have to be examined individually, but the apple-by-apple details could be left to the helper.

Conventional programming languages may be considered then “one-apple-at-a-time” languages with machine languages being a very primitive form, whereas array languages may be considered “all-the-apples-at-once” languages. In two of the following three sections we shall consider the array languages APL and its “modern dialect” J with an intervening section giving a short discussion of the array language Nial which was influenced by APL."

bsza · 2 years ago
> Pretty much looks the same in all C-like languages

Because it’s ancient; that doesn’t necessarily make it better or clearer. Look at it like you’ve never seen it before. It has an entire line of boilerplate smack bang in the middle. Sure, your brain filters it out because you’re used to it, but it’s still fugly. And it’s prone to typos/copypaste errors like all boilerplate.

theshrike79 · 2 years ago
Just the '++i' instead of 'i++' brought my reading that code to a halt and I had to start deciphering if it actually changes the operation of the loop.
haolez · 2 years ago
I like C++, but I don't understand this tradition of keeping these annoying namespaces prefixed to everything. Just get rid of those std::, foo::bar::whatever::, etc from your code and make it more readable. Use the "use" clause. It's very rare for such names to be ambiguous in the same file, unless I'm missing some bigger picture here.
porphyra · 2 years ago

    using namespace std;
is considered kinda bad since you don't want your namespace to be polluted with a bunch of std stuff. For example if you have `int count` lying around somewhere you'd want to be able to call `std::count` without fear of it being shadowed.

lupire · 2 years ago
Why would you not use std::sum(x) ?
constantcrying · 2 years ago
That is just a question of what you are used to. The "functional style" example is very obvious to a person who is familiar with that style. In fact if the rest of the code base is in a similar style it is the easier one to understand.

There is nothing inherent which makes either better or cleverer than the other.

(Also the first one is incomplete, as "n" is not defined, making the later more self contained)

kalkr · 2 years ago
Traditional C style for is terrible compared to what you can do with iterators in my opinion, but idk if C++ has those.

    list.iter().sum()
ezpz

neonsunset · 2 years ago
C++ makes this (and many other things!) needlessly painful.

In C# it is just

    return numbers.Sum();

vlovich123 · 2 years ago
Same for Rust.
tialaramex · 2 years ago
Where did "numbers" come from, and why are you so sure you can Sum() it? The original C code offered has some data structure (perhaps an array?) called x. Do C# arrays have a Sum method? I don't think so.

In Rust you would probably just write: x.iter().sum()

globular-toast · 2 years ago
sum(x) is easiest to read. If your language doesn't provide that as standard just define it yourself. In general you should always be idiomatic, though. If every C++ developer understands the accumulate version then use that.
leetcrew · 2 years ago
the readability issue here is in part that function arguments are purely positional in c++.

a little over the top for this simple example, but you can always create local variables to document intention.

  auto initVal = 0;
  auto accumulateOp = [](int a, b) {return a + b;};
  return std::accumulate(x.begin(), x.end(), initVal, accumulateOp);
I'd prefer to see a for each loop over algorithms functions in such simple cases, but I'd prefer almost anything over direct indexing when it isn't necessary. when I see that in new code, my first thought is always "what am I missing here?".

quotemstr · 2 years ago
One pet peeve of mine is people using std::for_each instead of a simple loop
jraph · 2 years ago
Same in JS. I once worked with someone who would use [].forEach and didn't like for loops because functional good, iterative loops bad. It was essentially in the coding style of the project and the for version would be rejected in MRs.

I don't miss this.

IMHO [].forEach just essentially expresses the same thing as a regular for loop, although in a more verbose and convoluted way that is also likely very inefficient because a JS compiler probably can't optimize away the function call per iteration, because of the very dynamic nature of JS. forEach probably has its use when you specifically need to call an existing function on each element, but I don't otherwise see the appeal.

phailhaus · 2 years ago
The other thing nice about the second style is that it's much easier to skim when part of a larger function, since it's a single type-checked statement. Keeps you from doing too much in it too.
GrantMoyer · 2 years ago
If you use C++23's ranges and Boost Lambda and sacrifice your better judgement, you can get it down to:

    fold_left(x, 0, _1 + _2)

sesuximo · 2 years ago
Imo it doesn’t matter at all. Both are fine. Surely there are more pressing things out there to think about!
williamcotton · 2 years ago
I haven’t written any C++ since the 90s. That is unrecognizable. Is that an anonymous function?
nine_k · 2 years ago
C++98 should be forgotten as a bad dream. C++17 is almost a sane and convenient language (as long as you remember about the boiling depths into which you can fall if you're careless).

C++ will live for very long, like Fortran. And also like Fortran, there now must be a serious and uncommon reason to start a new project in it.

jasonpeacock · 2 years ago
Kernighan's Law:

Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.

photochemsyn · 2 years ago
I'm finding LLMs invaluable in deciphering this kind of thing, with suitable prompting:

System: Always provide clear instructions from the perspective of an expert in the field. Double-check answers for logical coherence and factual correctness, using a consistent step-by-step methodology. Explain the benefits and disadvantages of different approaches in a compare-and-contrast style, and value security, readability, and organization over clever tricks.

User: Consider the following code function in Python: "def minimumTotal(self, t): return reduce (lambda a,b:[f + min (d,e) for d,e,f in zip(a, a[[1:],b)], t[::-1])[0]" . The goal is to rewrite this using only simple python bulitins like for loops. Use a step-by-step approach to dissassemble this code into a simpler format, and include plenty of comments.

User: Clarify what is meant, mathematically, by "the minimum path sum from top to bottom in a triangle"

Okay now I understand it... This is much easier if the original code has a set of robust tests you can run your LLM-generated code against, to make sure it works as advertised, but you can get the LLM to generate tests too if needed.

Now I want to go see how it does when faced with C obfuscation competitions.

vlovich123 · 2 years ago
Unless you got dumber between writing and debugging, it more likely means that it takes twice as long (or even more if you haven't touched it in a bit). It's unlikely that Kernighan meant it takes someone twice as smart to figure out what you were doing as that would be a nonsensical interpretation (someone twice as smart may not be able to figure out what the stupid person is trying to do in the first place if the code written was nonsensical).
fargle · 2 years ago
> It's unlikely that Kernighan meant it takes someone twice as smart to figure out what you were doing

no, that is exactly what he meant. clever code means you are just barely able to understand it enough to write it yourself [in any amount of time]. therefore, you aren't going to be able to debug it at all, by a factor of nearly two. and if you are the "smartest" person in the org (which he often was), then you are really in big trouble.

now obviously things in the real world like "smart" and "clever" are not one-dimensional quantities in neat categories, but he was making a memorable and funny quote, with quite a bit of truth to it, not a precise scientific hypothesis.

see also: "too smart for one's own good"

> nonsensical interpretation

how so?

> someone twice as smart may not be able to figure out what the stupid person is trying to do in the first place if the code written was nonsensical.

true, but irrelevant, this is about clever code from a smart person, not nonsense code from a stupid one.

bluGill · 2 years ago
Most of the problems I've had to debug the place to look is not clear. Sure I've had to fix "The is not spelled Teh", but most problems are it breaks in some weird situation - step one is figuring out where to even start looking. Once I narrow down the exact place to look it might be easy, but I have to hold a lot of different areas of code in my head while narrowing things down. Even when I narrow it down, sometimes the answer is make the code more complex, and if the code is already on the edge of how complex I can handle I won't know how to make it more complex.
matrix87 · 2 years ago
not smart enough to debug it in the same amount of time it took to write it
nine_k · 2 years ago
I keep to a simple rule: smart code is an asset, clever code is a liability.

Smart code is usually simple and clear, while also being short and efficient. Achieving this is not easy, but reading, understanding, and using smart code is easy. (Otherwise it's not smart, but, well, ordinary.) An example of smart code for me would be the merge sort algorithm, or the Lisp interpretation loop.

Clever code is usually some kind of last-resort hack, a trick, applied in dire straits, or to achieve a unique effect. An example would be the inverse square root hack, or the Duff device.

Beside the actual writing time, code review time is good for making code smarter and less clever.

dpc_01234 · 2 years ago
"Clever code" here means "code-golf-like code".

"Clever" is too subjective to be used like this. One person's clever, is another person's mundane, and it varies across languages, ecosystems, teams.

Also I'd like to point out that in this example, had that function had a comment and a couple of tests, then it wouldn't really matter much what the implementation is. If you don't like it, you can rewrite it your preferred way.

Though as a dev I'd be too lazy to try to optimize code for code-golf-like properties in the first place.

tehnub · 2 years ago
This is true, but in code reviews and such, it often boils down to familiarity above anything else, like someone preferring

    names = []
    for record in records:
        names.append(record["name"])
to

    names = [record["name"] for record in records]
Now, I might say something if I saw this:

    import operator
    names = list(map(operator.itemgetter("name"), records))
Seems a bit unidiomatic given that list comprehensions are in the language... but probably many disagree.

hleszek · 2 years ago
A simple list comprehension is quite readable and better IMHO. It is very common and most people will understand directly what it does.

Of course, when you have multiple levels or complex lambdas inside, then I agree that the for loop might be preferable.

amanzi · 2 years ago
Great example. My personal preference is the first one, but I put that down to me being inexperienced and not working in a professional development environment.

I've been using Ruff with most of the rules enabled, and they have a page for this scenario here: https://docs.astral.sh/ruff/rules/manual-list-comprehension/

Another one that trips me up is the ternary operator: https://docs.astral.sh/ruff/rules/if-else-block-instead-of-i... I prefer the longer, more verbose version, even though the suggestion looks more clever.

aidos · 2 years ago
Let me see if I can sway you.

The nice thing about both the ternary and the list compression is that they become statements of the form derived_thing = some_computation. The code flows better when you’re skimming it at a high level and thinking “then we get this”, “then pluck this”. You can think more about your reformed data and less about how it was reformed.

The alternative is that the branching obscures what you’re trying to create at each step.

Sometimes I’ll even use 2 list comprehensions instead of a loop (even though it’s slower) because it’s clearer to read something like:

    odds = […]
    evens = […]
That’s my experience, anyway.

sgarland · 2 years ago
I’m convinced that the only reason the operator library exists is to provide inputs for functools, which itself is mostly there to pretend that you’re a functional programmer.

…but I do kinda like map().

VTimofeenko · 2 years ago
I'd say it also heavily depends on the code style that's internal to the project. Case in point, for heavy toolz users:

    list(toolz.pluck("name", records))

akasakahakada · 2 years ago
list-map-itemgetter is actually the fastest solution in Python. Should actually promote this.