Ask HN: What are the best programming tricks you know?
During my studies, I was surprised that even a variable swap might be optimized to work in place by using XOR. Since then, I got surprised at least a few times more. For example when I was working a lot with SQL and found some engine-specific optimizations.
What were the simplest, or the most clever neat tricks you faced in your career? I mean both things simple and elegant, but also some really dirty hacks it's hard to believe can work.
182 comments
[ 1.3 ms ] story [ 237 ms ] threadIt sounds simple, but it's hard to have perfect discipline. Especially if you have a strong bias towards action like I do.
It's shocking how applicable this is! Don't code an algorithm before having a firm grasp of the solution. Don't write code in an interview before you're sure you've uncovered everything your interviewer wants. Don't slam out a UI before understanding how all of the edge cases should act. Unless the point of the code is exploratory, the code should be a foregone conclusion by the time you start.
The more you practice this, the more you'll uncover bugs and underspecifications before you've spent days going down the wrong path.
On the other hand, this diagramming does take hours and some people do fine with it. I can also do ok without it, but find it to generally be worth it, especially because I get interrupted all the time, so it's good to have a clear picture of the end goal.
As with all things, moderation is good here.
I have very limited memory, so I often write out pseudocode/notes about some properties to help my reasoning. For some problems, I will write scripts to try out some idea, verify my assumptions, while thinking about the problem.
I take inspiration from the famous pianist, Glenn Gould. He would study a piece of music for weeks or more. Taking all kinds of notes, truly studying and understand each measure, etc.. He did all of this without touching a piano.
Once he was satisfied, then he'd play the piece.
If you’re worried about lacking the discipline to throw away the prototype, do it in another language.
As I go through the list, I take the line and place it above the code.
Besides helping me go through things, it also helps anyone that comes in months after to work on it.
Even with modern IDEs this one neat trick saves a ton of annoyned confusion and swearing.
...OK, after having a bit of a poke around I would respectfully ask for a demonstration/citation of this insanity.
My own experience is that systems that panic get rather warm, which I blindly assumed was because the kernel had entered an infinite loop. This turns out to be the case: https://github.com/torvalds/linux/blob/cbe232ab07ab7a1c7743c...
There are references to interacting with kgdb, booting a crash kernel, and delaying before reboot above the highlighted spot in the previous link. This suggests that panicking is very much a "everything is torn to shreds and never comes back again" event.
Annoyingly I can't SIGKILL, -TERM, or -SEGV `init` easily (PID 1 is special-cased by the kernel), and don't remember exactly how to fire up my kernel initramfs test harness, so I can't properly test this easily at the moment. Am very curious though.
Instead of writing code that does what you want to do, have it return a description of what you want to do.
Then write an simple executor for such descriptions.
Why do this? Well it allows you to manipulate, test, store and inspect the description.
It's also completely insecure, since it executes arbitrary code. It's also hard to refactor, since there's always a chance that some caller's postprocess parameter is relying on e.g. a particular variable name, etc.
Here's a "defunctionalised" alternative:
Instead of allowing arbitrary code, we only accept a description of what to do (sort by "signup-date" in descending order). This requires we implement all the logic ourselves, as well as parsing and branching-on the input to determine what was requested.Defunctionalised APIs are the norm on the Web (except for those who accept raw SQL...); whilst functionalised APIs are the norm inside applications. For example, `myList.map(myArbitraryFunction)`. Defunctionalising is a good way to break systems into modules, with the descriptions acting as the interface.
Usual OOP solution is to have a wrapper on piano:
However, if we instead return a description of what to do, such manipulations compose far more easily: And it's easier to test: Wherever you are building a complex mock for testing consider defunctionalization instead.I have an html page with all my details, etc. Then say I want to create a markdown version of it, or a pdf version, or a docx version.
The first approach would be to manually rewrite my details to each format, as markdown, as pdf, as docx, essentially duplicating work each time.
The second approach, which is akin to defunctionalisation, would be to separate my details (data) from the format. So that would mean I would store my data in json format, then use a cli tool/program to generate it in html format, in pdf format, or in docx format.
I hope I got the basic idea right.
Edit: example
With the following test:public function testTransfer() { $juno = JunoAPISimulator::initialize($this->faker); $repo = AccountCreationRepoResolver::resolve();
btw defunctionalization usually means converting higher order functions into statically known function calls
Term from this blog post: https://www.gresearch.co.uk/blog/article/defunctionalisation...
If you have a function handling user sign up it often needs to build and write to multiple db tables. Split this into two functions a pure one that outputs the entities, and a second which takes these and handles writing them to a db inside a transaction. First the code is super simple to read, but more importantly it's easy to unit test since you don't need to mock anything, just make a list of input combinations paired with a list of expected outputs. Spend more time testing your business logic, less time verifying that postgres is working.
Transforming HTML into a pdf is trivial if you first iterate over the html and output a list of instructions that then get fed into a writer function one at a time, similar to how 3d printer instructions work.
Anytime I've done analysis work on a state that gets updated by a complex series of events it's always been worthwhile to build a discrete handler for each event type, then feed the events into it like a woodchipper. Complex problem solved with simple functions and a case statement.
I first heard of this in the great Functional Core, Imperative Shell [1] screencast of Destroy All Software. It allows you to easily unit test your functional core (as they're just pure functions).
[1] https://www.destroyallsoftware.com/screencasts/catalog/funct...
Means I can just grab some random subset of the search engine index, copy it over to my workstation, and load it up over there. It's also amazing for testing and inspection.
I.e. for a checkout:
- Make a temporary folder on the target disk (rollback: delete it)
- Create new files for those you have to change (rollback: delete them)
- Fill these files with their target content
- Move old files away into the temp folder
- Move new files into place
- If everything worked, delete the leftovers (the only thing that can't be rolled back)
Your disk fills up during checkout? Roll back!
You're on Windows and some file is blocked by another process? Roll back!
…etc. :)
Another classic book, a bit less down to the metal than Hacker's Delight (which deals with bit fiddling a lot):
Programming Pearls by Jon Bentley.
Figure out enough to get started. Then start. Don't think you can get a perfect view of something before wading into it, you'll just be wasting time. Likely your first attempt will suck so with your new found experience from starting and trying you can improve for your next attempt.
If not, "keep it simplE, Stupid!". The original is an exhortation to keep things simple, calling the recipient "Stupid" as a somewhat derogatory nickname (implying they would be, if they don't follow this advice).
With the -y turning the adjective "simple" into an adverb, this becomes an attribute of the only available adjective (well, only available word left in the sentence), "stupid", specifying in which manner said adjective is to be applied: simply stupid. (Like, plainly moronic.)
No, please don't keep things simply stupid. Keep them simple, Stupid!
For me this means walking through use-cases of an idea "on paper" while resisting the temptation to actually design the solution. Once you've defined the verbs/nouns of the system you're much better prepared to "dive in" and build something. That, in turn, will uncover things your paper system forgot or burst your illusions about certain use-cases causing you to rethink them. Rinse, repeat.
I've been the "overthink it" person, the person to spend way too much time on preparation only to start the implementation and realise one or many of the reasonable assumptions I've made are wrong and then have those affect everything else. I could have done a new on-paper solution for every single fork like that in the preparation phase (greatly extending it); or, I could have learned enough to _get started_ and come back to the drawing board to re-learn each time these (before: assumptions) are met.
It's a careful balance of preparedness and real-world discovery. Neither gives a complete picture without the other. Neither is even possible without the other.
Spending too long preparing is as bad as spending too little time. Too long and you account for possibilities that may not even exist, things you cannot know until you enter the system. Too little time and you're a bull in a china shop.
The `executeEffects` is still hard to test (you must mock / fake / stub things), but at least you only have to setup the mocking once.
Obviously, all hells break loose if you need to test some effects that depend on the result of other effects, which limits the technique to "single flow" of effects (printing stuff, sending commands, etc...)
Being jealous of keeping good Separation of Concerns comes to mind strongly regarding to this.
Well, that meant that we had to manage the texture memory as 2D regions, rather than as a 1D linear space. We spent two days thinking about creating a malloc for rectangular blocks. Then we went back and told the microcode people to fix the microcode. I think it was a one-line change for them.
So, yeah. Sometimes it's massively easier to push back on a stupid requirement than it is to implement it.
I've definitely gotten way more value of learning to google better and faster than being good at some editor or IDE shortcuts.
This also includes getting very familiar with the docs of the tools you use, knowing the ins and outs of them. Something you learn when you study for Red Hat / AWS / k8s certifications where you can consult docs is that the best way to be efficient during the exam is to be efficient looking at docs. Apply that to everything.
I can't even count how many this I should just apply that. It's an obvious thing but it takes time to getting used to clearly formulating your problems. Search engines are so sofisticated nowadays that you don't even necessarily need to know what you're looking for and it just pops up. I think of it as a sort of hive mind thing, internet became a natural extension of our brains.
Those moments when you are sitting there trying to figure out how to explain in search engine friendly terms the problem you have.
"glad to help, any time!"
Write code that is obvious, clearly readable by future programmers maintaining the code. Don't do clever things that are hard to read and reason about until there is a strong motivating reason to do so.
Honestly most of the clever things you're about to do, the compiler is already going to do for you, but better and without the bugs you'll introduce when you do it wrong.
>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
Reminds me of a similar 'wordplay as argument' favourite quote by Terry Pratchett:
> You know how dumb the average person is? Well, statistically, half of them are even dumber than that.
(source: https://www.youtube.com/watch?v=zLkg-S_FfXc&t=3720s )
I agree, with the caveat that does not mean low-level/unabstracted/verbose code. Whilst each line of such code might be "obvious" on its own, the big-picture behaviour and inter-dependencies can be difficult to follow across multiple pages.
As an extreme example, machine code is completely "obvious" (e.g. "increment register RAX"), whilst being notoriously hard to follow (often requiring disassemblers, etc.)
This is something I've come to realise more and more strongly as time's gone by. The code that I write isn't for me, it's for everyone coming after me. Including future-me. Gotta look after future-me.
I don't think this is actually the case, it's fairly easy to outsmart most compilers. A more honest argument is that it, in most cases, is a bad trade to make the code 3% faster by making it 10x more difficult to read.
Sometimes it is worth that effort, but that's the exception and you need to be able to motivate why it actually makes a difference. Show me a benchmark that demonstrates this is actually much faster (as a whole, not just the method), and then we can talk. "Clever" is in most cases another word for premature optimization.
Like, one thing that can be called a clever trick is to align the code vertically to make it easier to edit using vertical selections and macros in a text editor. That can actually make the code more readable and maintainable.
Honestly, I mostly mean things like "if we xor the two variables we can save one variable during the swap", etc. Clever code is not a great thing.
There is nothing that will stop a code review quicker than "I discovered a clever way to..." A dozen engineers are going to have to try to reverse engineer your cleverness in order to safely make any change to your code, so you'd better make double-sure that the performance you're buying with your cleverness is worth the total maintenance cost going forward.
There are some times it's worth it (the Fast InvSqrt hack and many others), but most of the time, it's just tickling our intellectual curiosity for our own benefit, and that's a bad trade.
It makes writing code so much faster it's not even funny, and with much less mental strain. Debugging sucks but testing rocks. You basically always save time.
I assume you're being downvoted for your slightly incorrect caveat.
Also, write tests.
The 'normal' way to write a loop over this is something like:
for (let i = 0; i < ls.length; i++) {
}A trick I got from sean barretts website (https://nothings.org/), is to use the for-loop initialization clause for the special case:
for (let i = 0, j = ls.length - 1; i < ls.length; j = i++) {
}The work I do usually does not care about the performance improvement if there is any, but it feels good to avoid that per-iteration check.
https://readline.kablamo.org/emacs.html
^This one cool trick will increase your productivity by 1000%!
I've had sr. engineers see me using ctrl-r and go "woah, how did you do that?"
https://readline.kablamo.org/vi.html
seriously, generics in typescript is godsent.
it's great to be able to code in javascript while having a very supportive vscode intellisense
I miss working with TS, it's just so much superior to old JS.