Ask HN: What are the best programming tricks you know?
182 comments
The most clever trick I've learned as a programmer is not to use clever tricks.
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.
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.
Reminds me of this quote by Brian W. Kernighan
>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
>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
Well, by assertion not by definition. The first half is a bald assertion. Never the less I love this.
Yeah it's more wordplay than argument.
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 )
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 )
> Write code that is obvious, clearly readable by future programmers maintaining the code.
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.
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.
> 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.
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.
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.
Hansen's Second Law: Clever is the opposite of maintainable
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.
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.
> Write code that is obvious, clearly readable by future programmers maintaining the code
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.)
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.)
There are so many things that can be called a "clever trick" that it makes no sense to say that you should not use clever tricks.
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.
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.
Then you have a strong motivating reason to do it.
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.
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.
[deleted]
Get really good at googling and bias to do it in more situations than you think you need and always earlier than you think you need. If you're working remotely do it live in meetings / planning as well and use it as an extension of your brain. There's no advantage in discussing a problem and only investigating it after you speak to someone if you can do it in parallel right away. If you treat the search engine as an extension of your brain rather than a crutch that means every google is you admiting you don't know something, you'll reap many rewards.
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'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.
>do it in more situations than you think you need and always earlier than you think you need.
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.
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.
> Get really good at googling
Those moments when you are sitting there trying to figure out how to explain in search engine friendly terms the problem you have.
Those moments when you are sitting there trying to figure out how to explain in search engine friendly terms the problem you have.
Nowadays I think you will find that if you speak to the search engine in the form of a question and in as plain terms as possible you'll have better results than by formulating it in keywords and google-fu like in the early days. I find that developers who "try to think in search engine" have gotten increasingly worse results over time as the search engine adapted to the general population's way of using it. This is all personal anecdata though, so you might want to try different ways that work for you.
This makes sense - however the search engine is just a proxy to the content on websites. I should have probably been more clear in my original comment. What I was really referring to, is when you are sitting there trying to think how other people might ask a question when the way you are originally asking it is not turning up good results.
Many times someone essentially solved their problem while in the act of formulating the question to ask me what to do about it.
"glad to help, any time!"
"glad to help, any time!"
Being able to use search engines is a definite skill that certainly cannot be understated. Once upon a time we'd have shelves of $100 books to pore over, and that would take time in itself. Being able to use the correct terminology and the correct search engine logic will help in delivering software faster.
@vasco Any tips/resources you would like to share on how to get better at googling?
in case you didn't know about it, google search operators are very useful to improve your queries (you can find them here for example : https://ahrefs.com/blog/google-advanced-search-operators)
Understand the problem and solution before coding.
It 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.
It 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.
OTOH, I find that writing some prototype code is one of the best ways to understand the problem and solution before starting to write the real program.
If you’re worried about lacking the discipline to throw away the prototype, do it in another language.
If you’re worried about lacking the discipline to throw away the prototype, do it in another language.
Great advice, that's why I bought https://runjs.app/ (not affiliated), I can quickly prototype pretty much anything with this and then port it to my actual target lang/framework after. Not hampered by the browser, npm, project setup or privacy concerns.
Your approach feels similar to mine, actually! I write lots of exploratory code (and mention it in my comment). I just view it as trying to understand the problem and solution instead of coding, since it's not usually the production code I'm going to commit.
I am biased toward over-planning. I have found your approach to work best for work I expect to take several days. In that case, actually putting together a lucidchart / draw.io diagram about what service will get refactored where is a huge help. It also keeps data flowing uni-directionally (yes, compiler will prevent circular dependencies, but there are ways to make code dirty without ever hitting that).
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.
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.
Hear hear! The generalization of this is figuring out how to prioritize multiple problems. Deciding which problems to solve in what order, and how much time to spend on each, is a difficult and important and never solved problem. But it’s quite valuable to practice and budget your time. This might sound different from understating the problem before coding, but it’s really almost the same thing, taking time to think about the approach carefully before diving in, making sure the requirements will be met, etc. Prioritizing multiple problems is very similar to planning the steps of solving a single problem. Maybe the main difference with the higher level prioritizing is that prioritizing has to be revisited periodically and/or continuously. Working in a software company, this is what the best PMs do really well, they figure out which features and bugs to work on that make the most difference, and they understand when to listen to the programmers and let them refactor things without making public-facing progress, and also when not to.
I am on the opposite extreme, I get so wound up designing stuff that I never write any code. I basically force myself to write something even it's imperfect and messy because it can be easily changed. And writing the code helps me understand the problem.
As with all things, moderation is good here.
As with all things, moderation is good here.
I guess this really depends on how complicated the problem really is and can you really know everything before you code.
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 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.
As I have aged, I have done this more and more.
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.
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.
how do you go about doing this? sometimes you dont know what will go wrong until you try it out and get feedback
iterate, prototype, patch, refine
Defunctionalisation.
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.
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.
So much this, really helps breakup complex tasks. Some examples off the top of my head:
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.
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'll add this: If you have to deal with state (e.g.: say you write something akin to `git checkout` that should rearrange the file system): Combine this strategy with the question of "can I roll this step back?".
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. :)
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. :)
+100
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...
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...
Would you please provide an example or direct towards some explanation? I can't seem to grasp what you mean exactly.
Suppose I have a procedure that plays a piano:
Usual OOP solution is to have a wrapper on piano:
const playTwinkleTwinkleLittleStar = (piano) => {
piano.play('c');
piano.play('c');
piano.play('g');
piano.play('g');
piano.play('a');
piano.play('a');
piano.play('g');
};
Now I want to have every note played twice. How can I do that?Usual OOP solution is to have a wrapper on piano:
const playItTwicePiano = (piano) => {
return {
play: (note) => {
piano.play(note);
piano.play(note);
},
};
};
playTwinkleTwinkleLittleStar(playItTwicePiano(piano));
However, if we instead return a description of what to do, such manipulations compose far more easily: const twinkleTwinkleLittleStar = () => {
return [ 'c', 'c', 'g', 'g', 'a', 'a', 'g' ];
};
const playItTwice = (sequence) => {
const xs = [];
for (const x of sequence) {
xs.push(x);
xs.push(x);
}
return xs;
};
And it's easier to test: it('should start with c', () => {
const xs = twinkleTwinkleLittleStar();
assert(xs[0] === 'c');
});
Wherever you are building a complex mock for testing consider defunctionalization instead.Ahh I see. So if I may use an analogy:
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.
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.
This is a great example, and feel the concept has "clicked" with me because of it. Thank you!
A simple example might be a function that makes an HTTP request to a server. You could separate out a function that builds the request from a function that sends the request to the server. Then it's easy to unit test that the request is what you expect, without coding up a realistic fake for the server.
You can't send a function/closure like data over the wire. You have to limit yourself to what kind of code you want executed in the future, but you can send or store it limitless.
Yeah I love this pattern. I'm using it for the data loading for my search engine. Instead of having the code that processes website data insert the data into the database and index directly, it essentially writes a program to a file, which is interpreted later to actually create the desired effects.
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.
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.
Somehow reminds me of monad...
btw defunctionalization usually means converting higher order functions into statically known function calls
btw defunctionalization usually means converting higher order functions into statically known function calls
Haskell monadic IO is basically this pattern, yes.
Term from this blog post: https://www.gresearch.co.uk/blog/article/defunctionalisation...
Term from this blog post: https://www.gresearch.co.uk/blog/article/defunctionalisation...
I also recommend this! I usually use the error handling of the language of choice for that purpose and implement the entire functionality returning errors/throwing Exceptions for the not implemented parts, and asserting upon in on unit tests to implement each pending functionality. I write the description on the assert message itself.
Edit: example
public function testTransfer() { $juno = JunoAPISimulator::initialize($this->faker); $repo = AccountCreationRepoResolver::resolve();
Edit: example
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class TransferHandler extends BaseHandler {
public function __construct(Generator $faker, DataStorage $storage) {
parent::__construct($faker, $storage);
}
public function handle(ServerRequestInterface $http_request): ResponseInterface {
$resource_token = $http_request->getHeader('X-Resource-Token')[0];
$account_id = $this->storage->account_id_by_resource_token[$resource_token];
if (empty($account_id)) {
return $this->responseInvalidResourceToken($http_request);
}
throw new \Exception('WIP -> store transaction data here for subsequent query, also issue notification');
}
}
With the following test:public function testTransfer() { $juno = JunoAPISimulator::initialize($this->faker); $repo = AccountCreationRepoResolver::resolve();
$account_id = $this->createAccountAndGetId(
$this->merchant,
fn(DigitalAccountRequestOperator $op) => $op->withProvider(DigitalAccountProviderSelector::JUNO())
);
try {
Executor::execute(new IssueTransferByBankDetails(
$account_id->toHex(),
"BRL 12.42",
$_ispb = '21018182',
$_agencyNumber = '0001',
$_accountNumber = '10000368021',
$_accountType = 'CHECKING',
));
$this->fail('should have thrown exception');
} catch (\Throwable $e) {
$this->assertEquals('WIP -> store transaction data here for subsequent query, also issue notification', $e->getPrevious()->getMessage());
}
}Can you give an example of this?
What? How?
Here's a "functionalised" API:
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:
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.
GET https://example.com/users?postprocess=result.sort("signup-date").reverse
This is easy to implement: just send the 'postprocess' parameter through 'eval()'. It's completely general, allowing the data to be sorted, filtered, transformed, etc. in any way the caller likes.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:
GET https://example.com/users?sort=signup-date&order=descending
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.
You write a simple interpreter basicly. Instead of directly manipulating the data , you build an ast of how to manipulate the data then you execute it. It also allows for nifty optimizations later on.
1. The Best Regex Trick[1], where you want to match Tarzan but not "Tarzan": use the regex "Tarzan"|(Tarzan) and ignore matches where the capturing group is empty.
2. When making UI adjustments of color, speed, position, or any other continuous value, find a way to map them to the mouse position (could be a slider, or simply reading the mouse position on a loop). It's a lot easier to find the right value when you can make 60 adjustments per second.
3. When creating a cache, think hard about what errors are temporary and what errors are permanent. Then make sure the permanent ones are also cached.
4. Always include a version number in your output, be it a field in your persisted data structures or your CLI stdout. Greatly helps troubleshooting deployments and the inevitable schema migrations.
5. Creating a concise Domain Specific Language to represent your inputs and outputs makes writing tests a breeze. For example, if you're writing a calculator, assertResult(FIVE+PLUS+THREE+EQUALS, "8").
6. The more you avoid strings, the more reliable will be your program. Trailing spaces, empty strings, wrong case, homoglyphs, smart quotes, typos, escape characters, ambiguous parsing, CRLF vs LF, tabs vs spaces, legacy encodings, NFC/NFD normalization, BIDI... If it was not typed on a keyboard by a human, there's probably a better data structure.
7. On a more practical note, Bash brace expansion[2]:
[2]: https://wiki.bash-hackers.org/syntax/expansion/brace
2. When making UI adjustments of color, speed, position, or any other continuous value, find a way to map them to the mouse position (could be a slider, or simply reading the mouse position on a loop). It's a lot easier to find the right value when you can make 60 adjustments per second.
3. When creating a cache, think hard about what errors are temporary and what errors are permanent. Then make sure the permanent ones are also cached.
4. Always include a version number in your output, be it a field in your persisted data structures or your CLI stdout. Greatly helps troubleshooting deployments and the inevitable schema migrations.
5. Creating a concise Domain Specific Language to represent your inputs and outputs makes writing tests a breeze. For example, if you're writing a calculator, assertResult(FIVE+PLUS+THREE+EQUALS, "8").
6. The more you avoid strings, the more reliable will be your program. Trailing spaces, empty strings, wrong case, homoglyphs, smart quotes, typos, escape characters, ambiguous parsing, CRLF vs LF, tabs vs spaces, legacy encodings, NFC/NFD normalization, BIDI... If it was not typed on a keyboard by a human, there's probably a better data structure.
7. On a more practical note, Bash brace expansion[2]:
echo word{1,2,3}
# word1 word2 word3
cp my_file.txt{,.bkp}
# cp my_file.txt my_file.txt.bkp
curl https://example.com/page/{1..100}
touch file_{001..100}.txt
# Zero-padded!
rm {file_a,file_b}{.c,.h}
# rm file_a.c file_a.h file_b.c file_b.h
[1]: https://www.rexegg.com/regex-best-trick.html[2]: https://wiki.bash-hackers.org/syntax/expansion/brace
You could harden a Linux server by killing the init process. That would cause a kernel panic, and prevent new processes running (e.g. shells). Existing processes like Web servers would continue to function, so you could put this at the end of the boot script :)
*Groans*
...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.
...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.
I was able to crash a systemd version 245 init on an ubuntu machine via gdb a few minutes ago as a test. It did max out the virtual CPUs on the ESXI host. And while it did not cause the machine to reboot, it seems to have taken down all the processes with it, as SSH and NGINX also crashed and froze the console as well...
Uh huh. And when the web server process crashes, how do you restart it?
Second server with a relay that controls the power strip for the first server.
How do you recover control of the server, if remote?
You can't; it's so secure that nobody can control it. Except via the reboot switch :)
Can’t shell the computer if I break the OS ;)
Brute force.
So very often you see programmers spending days or weeks trying to think up an elegant, efficient way to do something that will never ever make a meaningful contribution to the footprint of their code. It's one of those traps that becomes more subtle the more skilled you get, so nobody's safe.
Try the simple, obvious thing first. Need to store an arbitrary number of something? Stick them in a vector. Need to find something in an array? Loop through the array. Yes, there are exceptions, but by the time you find one you've already saved the time it took to identify and optimize that one case, ten times over, by not wasting time in the other 99% of your project.
So very often you see programmers spending days or weeks trying to think up an elegant, efficient way to do something that will never ever make a meaningful contribution to the footprint of their code. It's one of those traps that becomes more subtle the more skilled you get, so nobody's safe.
Try the simple, obvious thing first. Need to store an arbitrary number of something? Stick them in a vector. Need to find something in an array? Loop through the array. Yes, there are exceptions, but by the time you find one you've already saved the time it took to identify and optimize that one case, ten times over, by not wasting time in the other 99% of your project.
Fast inverse square root for computer graphics programming! [1] Popularized by leaked Quake III code (attributed to John Carmack), but dates back much further.
[1] https://en.wikipedia.org/wiki/Fast_inverse_square_root
[1] https://en.wikipedia.org/wiki/Fast_inverse_square_root
I completely forgot about that, but that's really one of the ugliest things I have ever seen :D
When implementing something:
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.
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.
Interesting how seemingly-at-odds this is from another highly upvoted trick: the one that begins "Understand the problem and solution before coding." Hard to find that balance sometimes between "just dive right in" and perfectionism.
I think you balance the two tricks by defining "Figure out enough to get started" as "Understanding the problem (without overthinking it)."
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.
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.
This is a better explanation of what I meant.
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.
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.
Great advice! I call that the "Mad Dash" - just try to get something working in a quick and dirty fashion. I often don't understand the problem until I work on the solution.
KISS = Keep It Simply Stupid
Uh... If that was intentional, then great joke!
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!
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!
Change the requirements. Understanding the requirements and adding your domain knowledge, often makes it possible to slightly change the requirements (in agreement with stakeholders) in a way that seriously reduces implementation time.
We were working on an accelerator board for a graphics system. It had a memory for textures to map onto shapes. The way the microcode was written, it wanted the texture memory to be "rectangular" - you would move the same distance in the source memory and the destination memory to start the next row.
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.
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.
What you are really saying is that people should develop influencing skills. OP starts by asking about programming tricks, but then mentions career, so it's hard to discern whether they just want some trivia. A programmer learning any soft skill will pay off exponentially in work and their personal life. The payoffs will be much bigger than any technical skill once two years into a career.
This might not be exactly what you were asking, but I found it to work well as the means to end with the best possible (given current knowledge) outcome, including considerations such as avoiding code that is too smart, or otherwise justifying and documenting it well.
Good commit messages. Why did I do what I did? How does it serve the end goal? What key decisions did I make? What were the trade-offs? What are the alternatives?
If I can't justify the commit, I can improve—and by doing this exercise, I will know where.
Ask reviewers to also review your commit messages. They might find gaps you missed, and it will help them in their code review.
Good commit messages. Why did I do what I did? How does it serve the end goal? What key decisions did I make? What were the trade-offs? What are the alternatives?
If I can't justify the commit, I can improve—and by doing this exercise, I will know where.
Ask reviewers to also review your commit messages. They might find gaps you missed, and it will help them in their code review.
Maybe not a 'trick' but writing comments before I dive into the code helps me structure my thoughts and then piecemeal the work.
Yep, I write comments to figure out what I'm doing, and then the code writes itself. It's gotten to the point where it's hard to code without writing the comment first, and usually the comment gets rewritten multiple times before I write a single line of code (thus saving me rewriting dozens of lines of code multiple times.)
I write comments and create lists for what should happen.
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.
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.
yep, I use them as a blueprint for what I'm going to write...
That's not necessarily a trick I meant, but definitely a good piece of advice. Do you keep them in the codebase after implementation or is it just for you while developing to sort things out?
I remove them unless they explain some of the trickiest part.
A 'trick' that I've grown fond of is for iterating over a circular list where the current and previous items form an edge, with a special case for the first edge, that is composed of the first and last items of the list (making it circular).
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.
The 'normal' way to write a loop over this is something like:
for (let i = 0; i < ls.length; i++) {
const j = i > 0 ? i - 1 : ls.length - 1;
...
}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.
Can you expand on your first trick? What is the problem being solved and what is a practical use case? I'm not sure I follow.
Write simple, clear code. Make sure it's readable. If you come back to something a few days later and you think it's hard to understand, add a comment.
Be as consistent as possible with your styles and naming conventions. A large project is significantly easier to read when every class, method, ect, follows very similar style guidelines.
Make sure that you don't need to "think" when reading code. (IE, you shouldn't have to page up and down to remember what type a variable is.) Often this is solved by including type in a variable name, and using some kind of convention to determine scope. (For example, always use "this." so you can tell that a field is a field instead of a locally-scoped variable.)
Try to follow a consistent pattern for line breaks, especially for when a line gets over ~120 characters.
IDEs that let you view code side-by-side on a wide monitor are very useful.
Invest in a giant monitor. I use a 50" 4k TV that I picked up on sale.
Edit: Always be skeptical of frameworks and 3rd party libraries. Put a lot of time into evaluating them, and choose wisely. It's always significantly more work to remote / replace a bad 3rd party framework / library than the time you put into choosing it.
Sometimes "no framework" is easier than a framework. Only writing a few queries? Consider skipping a complicated ORM. A clear startup sequence that builds a shared data structure of long-lived objects might, in the long term, be easier to debug than a complicated dependency injection framework.
Sometimes "no framework" is easier to learn than a complicated framework. Newcomers to your team may appreciate that it's easier to read 1000 lines of simple code instead of needing to spend days understanding a 3rd party framework.
Edit2: Don't underestimate the power of unit tests and automated tests; especially for long-lived industrial-strength software. Take the time to learn how to write good unit tests that you can quickly run in both your development environment and in CI. (Continuous integration)
Be as consistent as possible with your styles and naming conventions. A large project is significantly easier to read when every class, method, ect, follows very similar style guidelines.
Make sure that you don't need to "think" when reading code. (IE, you shouldn't have to page up and down to remember what type a variable is.) Often this is solved by including type in a variable name, and using some kind of convention to determine scope. (For example, always use "this." so you can tell that a field is a field instead of a locally-scoped variable.)
Try to follow a consistent pattern for line breaks, especially for when a line gets over ~120 characters.
IDEs that let you view code side-by-side on a wide monitor are very useful.
Invest in a giant monitor. I use a 50" 4k TV that I picked up on sale.
Edit: Always be skeptical of frameworks and 3rd party libraries. Put a lot of time into evaluating them, and choose wisely. It's always significantly more work to remote / replace a bad 3rd party framework / library than the time you put into choosing it.
Sometimes "no framework" is easier than a framework. Only writing a few queries? Consider skipping a complicated ORM. A clear startup sequence that builds a shared data structure of long-lived objects might, in the long term, be easier to debug than a complicated dependency injection framework.
Sometimes "no framework" is easier to learn than a complicated framework. Newcomers to your team may appreciate that it's easier to read 1000 lines of simple code instead of needing to spend days understanding a 3rd party framework.
Edit2: Don't underestimate the power of unit tests and automated tests; especially for long-lived industrial-strength software. Take the time to learn how to write good unit tests that you can quickly run in both your development environment and in CI. (Continuous integration)
Another thing: Be very careful using acronyms. When you use them in code, you better be 110% sure that a newcomer will understand them, or be able to learn them quickly. Instead, try to use the full work and rely on tab completion to save keystrokes.
When interacting with a system, e.g. to test it, or reproduce an error, etc. I try to build up a 'self-contained' command, rather than relying on state. For example, in a REPL we can usually press UP to get a copy of the previous command, which we can then tweak (I've added // comments to explain the though process; I wouldn't actually write them down!)
// Make sure we can load our dependencies, etc.
> import foo
Imported foo.bar, foo.baz, foo.theProblematicFunction
// Great, now let's try calling the problematic code
> import foo; foo.theProblematicFunction()
Type Error: foo.theProblematicFunction requires an argument of type User
// Hmm, it needs some arguments; let's keep plugging things in until it runs
> import foo; foo.theProblematicFunction(User())
Type Error: User require arguments of type (Name, Email)
> import foo; foo.theProblematicFunction(User(Name("Alice"), Email("alice")))
Type Error: Email requires username and host parts
> import foo; foo.theProblematicFunction(User(Name("Alice"), Email("alice", "example.com")))
LOG.INFO: Sent welcome email to [email protected]
// Great, we can run the code. Now let's try reproducing the error. Maybe it was spaces in the Name?
> import foo; foo.theProblematicFunction(User(Name("Alice Doe"), Email("alice", "example.com")))
LOG.INFO: Sent welcome email to [email protected]
// Nope that works. Maybe it was the lack of a top-level domain?
> import foo; foo.theProblematicFunction(User(Name("Alice"), Email("alice", "localhost")))
Uncaught InvalidInputException: Email domain must contain a dot
// Aha, we've found the problem!
We now have a single line `import foo; ... "localhost")))` which reproduces what we're after. We can put that in a test suite to make a regression test; or email it to a colleague for their opinion; or file a bug in an upstream library; etc. This would be trickier we had done separate lines like `import foo`, `let name = Name("alice")`, etc.One that a professor of mine used when I was in grad school which I don't see that often these days is the use of sentinel values. For arrays/lists of unknown sizes, it's sometimes nice to do something like:
A variation on this can be used to stop queue processing in a multi-threaded/processing environment. You can put N sentinels (aka, poison) in the queue for N readers, and when a reader gets the poison, it quits.
while val != SENTINEL:
# ...stuff...
val = some_iterator()
You just have to be careful that you can never have real data equal the sentinel.A variation on this can be used to stop queue processing in a multi-threaded/processing environment. You can put N sentinels (aka, poison) in the queue for N readers, and when a reader gets the poison, it quits.
Sentinel values are too often found in real data eventually, which is why they're most often used in academic environments, and by fresh grads. First or second time the sentinel value that could never possibly be found in real data is found in real data, you start looking for other solutions.
Back in the 1980s, the statistics package -- application, really, but this was before that word had become wide-spread in academic environments for that sort of thing -- Statgraphics used -32768[1] for "Missing Value", i.e. "NULL".
Had to fudge our actual -32768 values to -32767.
___
[1]: The signed-integer value of a word full of set bits ('1' values) on a 16-bit twos-complement processor like the Intel 80286? Something like that.
Had to fudge our actual -32768 values to -32767.
___
[1]: The signed-integer value of a word full of set bits ('1' values) on a 16-bit twos-complement processor like the Intel 80286? Something like that.
'\0' (NUL) terminated strings in C is the idiomatic example for sentinel values.
Replacing arrays of structures with structures of arrays.
https://en.wikipedia.org/wiki/AoS_and_SoA
https://en.wikipedia.org/wiki/AoS_and_SoA
Eric Lippert taught me that if you see a bug in some tricky code, rewrite the code with a simpler approach, rather than put in a fix which will make the code even trickier and quite possibly create new bugs.
More specifically, if you have a bunch of bugs related to storing data in an inconvenient way, convert the data to a simpler format rather than write a bunch of complex logic around the bad data format.
Article here: https://docs.microsoft.com/en-us/archive/blogs/ericlippert/b...
As a tangent, this article took me ages to find, even though I remembered the author, most of the details, and the string "SensibleDate". I'm not sure if the lesson is that MS is at fault for moving old content and having a lousy internal search tool, or Google is getting worse because it couldn't find it anyway, or I've gotten worse at finding stuff. But a few years ago I would've been shocked to not quickly find an article where I remembered a few relevant details, and now it seems like the norm.
More specifically, if you have a bunch of bugs related to storing data in an inconvenient way, convert the data to a simpler format rather than write a bunch of complex logic around the bad data format.
Article here: https://docs.microsoft.com/en-us/archive/blogs/ericlippert/b...
As a tangent, this article took me ages to find, even though I remembered the author, most of the details, and the string "SensibleDate". I'm not sure if the lesson is that MS is at fault for moving old content and having a lousy internal search tool, or Google is getting worse because it couldn't find it anyway, or I've gotten worse at finding stuff. But a few years ago I would've been shocked to not quickly find an article where I remembered a few relevant details, and now it seems like the norm.
Using cryptographic hashes for "deterministic randomness", i.e. values which are reproducible, but unpredictable beforehand.
I've used this for benchmarking, seeding pseudorandom number generators with the SHA256 of the project's source code. That way, anyone can reproduce the benchmark, but it's hard to "cheat" since tweaking the code (e.g. to favour the current benchmark) will alter the benchmark in an unpredictable way!
I've used this for benchmarking, seeding pseudorandom number generators with the SHA256 of the project's source code. That way, anyone can reproduce the benchmark, but it's hard to "cheat" since tweaking the code (e.g. to favour the current benchmark) will alter the benchmark in an unpredictable way!
You really don't need cryptographic hashes for this, as they pose more demands than you actually need at the expense of speed. Something like CityHash or Murmur3 will be more than sufficient.
I suppose I might try being the grump that says the best trick is to not do any tricks. As in don't be cute[a], I guess.
There was a series[b] of 'Dirty Coding Tricks'[0], originally posted on Gamasutra from 2009. I remember getting something out of them at/around the time, maybe you'll find something of interest. I think most were more like "hacks I can't believe we shipped" rather than tricks, but you did mention dirty hacks, so...
* [a] Is "don't be cute" not a thing? -- I'm struggling to find usages/source of the phrase right now
* [b] Well, I remember a series of them but I could be wrong. To find out either way would require navigating the hellscape that is informa's gamedeveloper magazine's website's repackaged Gamasutra content which happens to be unavailable due to "navigating some downtime on our legacy web pages".
* [0] https://web.archive.org/web/20220707000222/https://www.gamed...
There was a series[b] of 'Dirty Coding Tricks'[0], originally posted on Gamasutra from 2009. I remember getting something out of them at/around the time, maybe you'll find something of interest. I think most were more like "hacks I can't believe we shipped" rather than tricks, but you did mention dirty hacks, so...
* [a] Is "don't be cute" not a thing? -- I'm struggling to find usages/source of the phrase right now
* [b] Well, I remember a series of them but I could be wrong. To find out either way would require navigating the hellscape that is informa's gamedeveloper magazine's website's repackaged Gamasutra content which happens to be unavailable due to "navigating some downtime on our legacy web pages".
* [0] https://web.archive.org/web/20220707000222/https://www.gamed...
It's not necessarily something you'd use today(?), but Duff's Device has always struck me as particularly beautiful:
https://en.wikipedia.org/wiki/Duff%27s_device
send(to, from, count)
register short *to, *from;
register count;
{
register n = (count + 7) / 8;
switch (count % 8) {
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while (--n > 0);
}
}
> In the C programming language, Duff's device is a way of manually implementing loop unrolling by interleaving two syntactic constructs of C: the do-while loop and a switch statement. Its discovery is credited to Tom Duff in November 1983, when Duff was working for Lucasfilm and used it to speed up a real-time animation program.https://en.wikipedia.org/wiki/Duff%27s_device
Around 25 years ago, I was part of a team that was building a client-server workflow system in C++. We had sharded SQL databases and an object server that managed the ORM for each shard and a queue server that kept a prefetched list of objects for each queue. A central broker handled client applications and communicated with the queue server and the object server and also handled transaction management. Since this was a workflow, objects moved from queue to queue as defined in the buisness process and were acted upon by applications (could be an automated step or user-driven).
One of the main techniques we used was a memory manager (using overloaded new/delete operators) and data serialization such that we could build the prefetched set of objects in a memory manager (at the object server) and persist it to disk on a central file server. The client could just load that file into memory and directly access the objects without any parsing. We had a whole object API to encapsulate this. When the client was done with processing, it would just flush the memory to disk (on the network file server) and send a message the broker and the broker/objectserver/queueserver handle the write lazily.
Also, if the system crashed, we had a tool to rebuild the object server state and queue server state from the SQL databases, so there was some recovery/resiliency built in.
Granted, the operating environment of the client and server was controlled by us and this architectural choice made sense. But, the main thing that surprised me was the performance gained by the client apps not having to parse out the objects due to the use of the memory manager and the reduction of application latency (for users) due to the interleaving of prefetch and lazywrites. This was by no means a simple trick, but certainly very clever.
One of the main techniques we used was a memory manager (using overloaded new/delete operators) and data serialization such that we could build the prefetched set of objects in a memory manager (at the object server) and persist it to disk on a central file server. The client could just load that file into memory and directly access the objects without any parsing. We had a whole object API to encapsulate this. When the client was done with processing, it would just flush the memory to disk (on the network file server) and send a message the broker and the broker/objectserver/queueserver handle the write lazily.
Also, if the system crashed, we had a tool to rebuild the object server state and queue server state from the SQL databases, so there was some recovery/resiliency built in.
Granted, the operating environment of the client and server was controlled by us and this architectural choice made sense. But, the main thing that surprised me was the performance gained by the client apps not having to parse out the objects due to the use of the memory manager and the reduction of application latency (for users) due to the interleaving of prefetch and lazywrites. This was by no means a simple trick, but certainly very clever.
That structure is infinitely more important than algorithms for the great majority of the cases you have to deal with when building a product.
Being jealous of keeping good Separation of Concerns comes to mind strongly regarding to this.
Being jealous of keeping good Separation of Concerns comes to mind strongly regarding to this.
ZeroMQ [1] - it's sockets on steroids.
I've used it to do distributed load testing using a small javascript program, a script written in python to cache aggregated results for a big database and as a transport for high performance messaging in a c++ framework.
Being able to do distributed computing without having to rely on a single framework or setting up lots of additional infrastructure is really useful!
I've used it to do distributed load testing using a small javascript program, a script written in python to cache aggregated results for a big database and as a transport for high performance messaging in a c++ framework.
Being able to do distributed computing without having to rely on a single framework or setting up lots of additional infrastructure is really useful!
I use property-based testing a lot:
- We write our unit tests as functions, accepting some arguments
- Our assertions should hold for all possible values of those arguments (this works better with a rich type system!)
- The test framework generates a bunch of random values, to check if our test functions ever fail
This on its own is a great way to find edge-cases, and expose hidden assumptions.
One of the most hacky things I've done with it is to regression-test interactions between dynamic binding and futures. My test suite defined a small language, containing strings, concatenation, dynamic variables and futures (deferring and awaiting). I wrote a straightforward, single-threaded implementation which just computed deferred values immediately; and I wrote a concurrent implementation using actual dynamic binding and futures. My property-tests would generate random expressions in this language, and check that both interpreters give the same result.
- We write our unit tests as functions, accepting some arguments
- Our assertions should hold for all possible values of those arguments (this works better with a rich type system!)
- The test framework generates a bunch of random values, to check if our test functions ever fail
This on its own is a great way to find edge-cases, and expose hidden assumptions.
One of the most hacky things I've done with it is to regression-test interactions between dynamic binding and futures. My test suite defined a small language, containing strings, concatenation, dynamic variables and futures (deferring and awaiting). I wrote a straightforward, single-threaded implementation which just computed deferred values immediately; and I wrote a concurrent implementation using actual dynamic binding and futures. My property-tests would generate random expressions in this language, and check that both interpreters give the same result.
If you are interested in these kinds of things I can recommend the book "Hacker's Delight".
Great book indeed. By Henry S. Warren, ISBN 0-321-84268-5.
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.
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.
One of my favorite tricks (from CPU designers I worked with) was to "poison" requests or pieces of data and pass them on, instead of trying to return them immediately after an error. At first it seems like it's just wasting cycles, but often "turning around" the control flow at a particular point can require all sorts of code contortions especially when locking is involved. Letting things flow to a point where it's easier to turn around - like turning around in a parking lot instead of in the middle of the street - has drastically simplified error handling in several codebases I've worked on. It's like soft deletes, but for data in flight instead of data at rest.
Writing functions that return side effects as a message instead of running them within the function must be one of the most underrated things.
Can you elaborate? This in the context of a message-passing architecture? Or do you mean as callbacks?
This is often called defunctionalisation
Can you give an example?
If your function is supposed to perform some side effects, and you want to test complex cases where the effects are difficult to verify, than changing the function from
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...)
function foo() : void {
doBar();
if (...) {
doBaz(12);
}
}
to something like: function fooEffects(): Effects[] {
results = [ { type: 'bar' }]
if (....) {
results.push({ type: 'baz', value: 12 })
}
return result
}
function executeEffects(effects: Effects) {
if (effects.type === 'bar') {
....
}
if (effects.type === 'baz') {
...
}
}
Then you can easily test the "fooEffects" function (it just return values that you can compare to the expected effects").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...)
As far as "tricks" go, my favorites are:
* Recursive descent parsing, see https://craftinginterpreters.com/ for a great introduction
* ConcurrentHashMap#computeIfAbsent() for a thread safe caching mechanism: https://www.geeksforgeeks.org/concurrenthashmap-computeifabs...
* Using Hypermedia As The Engine of Application State (https://htmx.org/essays/hateoas/ :)
* Recursive descent parsing, see https://craftinginterpreters.com/ for a great introduction
* ConcurrentHashMap#computeIfAbsent() for a thread safe caching mechanism: https://www.geeksforgeeks.org/concurrenthashmap-computeifabs...
* Using Hypermedia As The Engine of Application State (https://htmx.org/essays/hateoas/ :)
In C++ you can access a private member of a class A by defining a class B as exactly the same but making that member public, and casting to B*: B* b = (B*)&a; b->private_member... Shouldn't be used more than once in every 20 years though :)
This first seems like a pointless thing to do because you could just make it public in class A. But then when I think about it more I realize that it might actually be pretty useful if you have a huge codebase and it would take 30 minutes to recompile if you change class A that is probably defined in a header file that is included everywhere. This trick can then save 30 minutes.
This is useful when class A is from system or 3rd party library header so you can't just change it (unless you want to maintain the patch).
Being proficient and fluent in your language and tooling of choice is a superpower. What are the guarantees you get with different kinds of async behavior, how are null or maybe values handled and passed around, what are the default values of things and why. Not just knowing, but understanding questions like these will allow you to write code that is robust and has fewer unexpected side effects. In short, the trick is putting in the hours and hard work. Or, to quote MtG: "There is no shortcut to work done true and well."
For performance optimizing, learning first how to do less work & delete code, second how to avoid memory accesses, and third how to use cache friendly accesses, are pretty broadly useful.
One bit-twiddling trick I used successfully on a GPU recently that was quite satisfying: I needed to gather some data to process, and it was a different amount of data for each thread. GPUs are SIMD, so process multiple threads with the same instruction and it’s bad if one thread does different work than it’s neighbor. I first tried sorting the data to make sure the work was coherent and to minimize the number of divergent threads - threads working on data while other threads are idle or doing something else. Then I learned how to use an FFS (find first set) instruction to avoid the sort but achieve exactly the same behavior. Setup a bitmask to mark active data, each thread then uses FFS to find the index of a datum to process, clears the bit and processes one item, repeat until the bitmask is zeroed. Much better than sorting, and uses fewer memory accesses, win!
Also you might be surprised to learn that the XOR trick for swapping variables is rarely if ever faster than plain old assignment on modern architectures. This is one bit-twiddling trick that you could probably unlearn and never need. It’s cute and fun to know, might be useful in an interview once in a while, but I’ve never seen it unused in production even though I have seen it discussed many times.
One bit-twiddling trick I used successfully on a GPU recently that was quite satisfying: I needed to gather some data to process, and it was a different amount of data for each thread. GPUs are SIMD, so process multiple threads with the same instruction and it’s bad if one thread does different work than it’s neighbor. I first tried sorting the data to make sure the work was coherent and to minimize the number of divergent threads - threads working on data while other threads are idle or doing something else. Then I learned how to use an FFS (find first set) instruction to avoid the sort but achieve exactly the same behavior. Setup a bitmask to mark active data, each thread then uses FFS to find the index of a datum to process, clears the bit and processes one item, repeat until the bitmask is zeroed. Much better than sorting, and uses fewer memory accesses, win!
Also you might be surprised to learn that the XOR trick for swapping variables is rarely if ever faster than plain old assignment on modern architectures. This is one bit-twiddling trick that you could probably unlearn and never need. It’s cute and fun to know, might be useful in an interview once in a while, but I’ve never seen it unused in production even though I have seen it discussed many times.
Learning to use a profiler (and using a good profiler) is also pretty key to optimization. It's not always obvious where the bottlenecks are, especially in parallel code. Because of Amdahl's law, the code that consumes 90% of the CPU cycles may not be where you should look to make the code faster, sometimes it's that innocent looking 10% segment instead.
If you're not doing TDD, write the name of the tests before coding.
Why?
* It helps you to understand the problem and the solution;
* I tend to write more test scenarios and cover more cases when I don't need to write the tests right away. I write the names, the actual test it's a problem for my future me;
* You don't have to think and remember of all the scenarios after writing the code, just do what the name says.
* No one actually does TDD lol - jokes a part, TDD might be annoying in some complex cases IMO.
* It helps you to understand the problem and the solution;
* I tend to write more test scenarios and cover more cases when I don't need to write the tests right away. I write the names, the actual test it's a problem for my future me;
* You don't have to think and remember of all the scenarios after writing the code, just do what the name says.
* No one actually does TDD lol - jokes a part, TDD might be annoying in some complex cases IMO.
The command design pattern never ceases to amaze me. Package up a complicated process in an "input, process, output" package. Turn it into a black box. The input should be as small as possible, just the details and data you need to "do the needful". The output should be as little as possible to explain how things went. I usually let my code grow organically, but once it gets to a certain point, using this technique really helps clean things up.
Isn't that just a function?
Yes. There's some nuance to it, but at the end of the day, yes it is just a function call. Usually the input and output are complex types which express the desired behavior or parameters of the process. There's some conceptual distance between a function that takes two integers and returns an integer, and one that takes a complex series of mutually related arguments, some possible business objects with child objects attached, etc.
A Python "trick" I like which is a compact one-liner if-statement.
var = (False, True)[predicate]
This is equivalent to: var = True if predicate else False
Since we are doing item getting, we can do CASE statements. var = (case0, case1, case2)[item]
Which is equivalent to: if item == 0:
var = case0
elif item == 1:
var = case1
elif item == 2:
var = case2All-pairs testing.
Lets say you've got a complicated piece of code that has a bunch of options (either input or configuration, doesn't matter). You can't test it exhaustively because that's exponential in the number of options. But you can test all combinations of each pair of options, in ~O(A x B) where A and B are two largest option cardinalities.
To be concrete about this, suppose there are 10 option variables each having at most 5 possible values, a,b,c,d,e,f,g,h,i,j. Suppose that d and e are have the largest cardinality (5). So you can test all pairs of assignments to d and e in 25 tests. But with the same 25 tests (or epsilon extra ones) you can test all pairs of assignments to every pair of variables - you need hardly any more tests.
Of course, coming up with the table of values is a bit tricky, especially if there are some constraints. But there are libraries to do it.
Takeaway: with a very small number of tests you can find corner cases that are very hard to locate with a standard approach.
This generalises to triples, etc.
(see https://burtleburtle.net/bob/math/jenny.html)
Lets say you've got a complicated piece of code that has a bunch of options (either input or configuration, doesn't matter). You can't test it exhaustively because that's exponential in the number of options. But you can test all combinations of each pair of options, in ~O(A x B) where A and B are two largest option cardinalities.
To be concrete about this, suppose there are 10 option variables each having at most 5 possible values, a,b,c,d,e,f,g,h,i,j. Suppose that d and e are have the largest cardinality (5). So you can test all pairs of assignments to d and e in 25 tests. But with the same 25 tests (or epsilon extra ones) you can test all pairs of assignments to every pair of variables - you need hardly any more tests.
Of course, coming up with the table of values is a bit tricky, especially if there are some constraints. But there are libraries to do it.
Takeaway: with a very small number of tests you can find corner cases that are very hard to locate with a standard approach.
This generalises to triples, etc.
(see https://burtleburtle.net/bob/math/jenny.html)
Not strictly a programming trick, but when something breaks I do this mentally:
1. Don't change anything.
2. Ask myself what do I know.
3. Ask myself why do I think I know (2).
4. Ask myself what to do next.
The step 3 is really the big one.
1. Don't change anything.
2. Ask myself what do I know.
3. Ask myself why do I think I know (2).
4. Ask myself what to do next.
The step 3 is really the big one.
I like code-golf challenges. To write shorter code, you have to first find an algorithm that's short to write and also know all the ways you can re-write existing statements to make them shorter (which are often language-specific). This forces you to learn operator priorities (when you can avoid parentheses), what are default variable values, short-circuited statements, when you can re-use the same variable or ways to use built-in functions for things other than they were built for, etc.
Some code-golf tricks examples, for JavaScript: https://www.geeksforgeeks.org/code-golfing-in-javascript/
A few examples, if you skip the link:
Some code-golf tricks examples, for JavaScript: https://www.geeksforgeeks.org/code-golfing-in-javascript/
A few examples, if you skip the link:
// Check if variable is upper case
'g' < {} // Returns false, lower case
'G' < {} // Returns true, upper case
If you were to use a 'g' < 'a' you would need an extra character. // Ceil
a = Math.ceil(a);
a = a%1 ? -~a : a;Best programming trick I know - when I don't know something, I ask for help.
Not sure if this counts as a "programming trick", but for maneuvering around the shell: learning readline.
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/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?"
Most of the time I know the starting characters of the command I want, so I use up/down arrow instead of Ctrl+r. You'll need this in `.inputrc`
# use up and down arrow to match search history based on typed starting text
"\e[A": history-search-backward
"\e[B": history-search-forwardYeah I heard of people grepping their history file. Ctrl+R is so much faster.
And for the rest of us :)
https://readline.kablamo.org/vi.html
https://readline.kablamo.org/vi.html
Treating DOM as the state of my JavaScript code instead of storing state separately and then synchronizing.
Using HTML instead of parsing Markdown by applying white-space: pre-line with some additional styling for semantic tags.
Styling style and script tags as visible to embed examples into HTML pages without duplicating or entity-encoding them.
Using files as a key-value store. (File name = key. File content = value.)
Using folders/files as a one-to-many messaging bus. E.g. one app creates empty do-something.txt file in a folder, couple other apps monitors the folder and do stuff. Very simple setup, but allows to create multiagent systems in different languages with persistence and easy way to debug/interact with the whole thing manually.
Using inheritance instead of mocking frameworks. (Instead of mocking something out, extend the class, override "mocked" behavior and test that class.)
Using HTML instead of parsing Markdown by applying white-space: pre-line with some additional styling for semantic tags.
Styling style and script tags as visible to embed examples into HTML pages without duplicating or entity-encoding them.
Using files as a key-value store. (File name = key. File content = value.)
Using folders/files as a one-to-many messaging bus. E.g. one app creates empty do-something.txt file in a folder, couple other apps monitors the folder and do stuff. Very simple setup, but allows to create multiagent systems in different languages with persistence and easy way to debug/interact with the whole thing manually.
Using inheritance instead of mocking frameworks. (Instead of mocking something out, extend the class, override "mocked" behavior and test that class.)
I like to implement features from end-to-end and keep them as lean as possible so I can fit the execution flow in my head. Each time an edge case or a question pops up, I leave a TODO in the code explaining why it could be a problem but I don't solve it yet. Then I proceed to implement the next call in the stack. Once the happy path is done and tested, I go back the TODOs.
I also like to implement the feature top-down: starting with the caller and implementing the chain of callee. It helps designing the abstraction layers by forcing you to answer "does the caller care/know about this argument or should it abstracted away elsewhere (config or service)?" By starting from the top, you tend to add arguments only if they are needed.
I also like to implement the feature top-down: starting with the caller and implementing the chain of callee. It helps designing the abstraction layers by forcing you to answer "does the caller care/know about this argument or should it abstracted away elsewhere (config or service)?" By starting from the top, you tend to add arguments only if they are needed.
Here's a trick I wrote with `GNU grep`:
You can also just use `cat -s` if you don't need a custom group separator and don't care about start/end empty lines.
grep --group-separator= -A0 '.'
The above command is equivalent to the below `awk` command (including removal of empty lines at start/end of input): awk -v RS= '{print s $0; s="\n"}'
Both the above solutions can be changed to a different group separator instead of single empty.You can also just use `cat -s` if you don't need a custom group separator and don't care about start/end empty lines.
$ printf '\n\n\ndragon\n\n\n\n\nunicorn\n\n\n' | cat -s
dragon
unicorn
$ printf '\n\n\ndragon\n\n\n\n\nunicorn\n\n\n' | awk -v RS= '{print s $0; s="\n"}'
dragon
unicornTest-driven development. You write the unit tests before the code, then you write code until tests stop failing.
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.
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 don't disagree, but it is best if you know the problem already. And it won't change too substantially, in which case you have to rework the code and the tests.
Try and talk to your users directly and if you're attempting to improve their workflow, try and watch their existing workflow and ask them questions about why they do things as they are now.
Also use lists and hash-maps first, be wary of exotic features.
When building UIs, I fill the UI with TODO bullet points. This screen will have textbox, when I press enter it will store the input.
Next version: This textbox should display properly on mobile. Then I fix it.
So my UI is always telling me what to work on next
Next version: This textbox should display properly on mobile. Then I fix it.
So my UI is always telling me what to work on next
For C, use seccomp and write simple and readable code and hope the compiler optimizes instead of complicated and hard to audit code, I don't mean commenting and variable/function names but the actual control flow, data structures and algorithms. Eliminate complexity unless you absolutley have no choice. And the power 10 rules from JPL which i think can apply to other languages as well.
https://en.m.wikipedia.org/wiki/The_Power_of_10:_Rules_for_D...
https://en.m.wikipedia.org/wiki/The_Power_of_10:_Rules_for_D...
Track down a syntax-aware code search/database tool (if your IDE doesn't have one). For me it was/is GNU ID Utils. At some point you'll be handed thousands or millions of lines of someone else's code, and you will need to find things quickly and completely.
Also, turn on all compiler warning flags all the time. Some compiler warnings aren't triggered unless optimization is enabled, so turn that on every now and then during development and see if any warnings pop. If you're handed thousands of lines of someone else's code and it's riddled with warnings, fix them first.
Also, turn on all compiler warning flags all the time. Some compiler warnings aren't triggered unless optimization is enabled, so turn that on every now and then during development and see if any warnings pop. If you're handed thousands of lines of someone else's code and it's riddled with warnings, fix them first.
> If you're handed thousands of lines of someone else's code and it's riddled with warnings, fix them first.
If you can't, capturing them in a way that lets you do a diff at least helps you notice when you've introduced a new warning. The ease of doing this depends more than a bit on the particular ecosystem.
If you can't, capturing them in a way that lets you do a diff at least helps you notice when you've introduced a new warning. The ease of doing this depends more than a bit on the particular ecosystem.
The simplest trick that every programmer learns early: when parenthesising, write both parentheses first: (), {}, [], etc. Then fill them in.
Even with modern IDEs this one neat trick saves a ton of annoyned confusion and swearing.
Even with modern IDEs this one neat trick saves a ton of annoyned confusion and swearing.
I don't like to do this, I like having an editor setup to highlight the open parent that matches the one you are closing. Then I can confirm that my structure matches my intention.
Someone here said don’t name your variables x, or a, etc.
I argue that you do name them ambiguously to start.
Normally when you’re writing code you’re thinking about the right hand side of the statement. What is the next step in my code? But most of the time we write the left hand side first. If you’re focused on naming, you lose your flow. You’re switching contexts.
By choosing x or a as your variable name, it unblocks your flow to write the right hand side.
Once you’ve written the code, go through and rename your variables. Use your IDE refactoring features to make this easy and change it everywhere at once.
I argue that you do name them ambiguously to start.
Normally when you’re writing code you’re thinking about the right hand side of the statement. What is the next step in my code? But most of the time we write the left hand side first. If you’re focused on naming, you lose your flow. You’re switching contexts.
By choosing x or a as your variable name, it unblocks your flow to write the right hand side.
Once you’ve written the code, go through and rename your variables. Use your IDE refactoring features to make this easy and change it everywhere at once.
This is a trick that makes the experience of writing C and C++ code much better: Skip the header files and include the .c or .cpp files into one translation unit instead.
* No more code duplication every time you change the type or name of a function.
* Fewer files and less code.
* Tab completion works better for the file names (you don't have two files that start with the same name)
* Only one place to put the comment if you want to document the function (otherwise the comments are easy to miss if there is a description in the header file but I'm looking at the definition).
* The code usually compiles faster.
* No more code duplication every time you change the type or name of a function.
* Fewer files and less code.
* Tab completion works better for the file names (you don't have two files that start with the same name)
* Only one place to put the comment if you want to document the function (otherwise the comments are easy to miss if there is a description in the header file but I'm looking at the definition).
* The code usually compiles faster.
(1) Model checking first (using a tool most appropriate for the domain): UPPAAL, SPIN, NuSMV, Z3, TLA+
(2) Model based design (less time spent on less value-added concerns...plus, capturing even more benefit from (1))
(2) Model based design (less time spent on less value-added concerns...plus, capturing even more benefit from (1))
- Use cs.github.com for code search when you need to find usage ( other than sample) for a library
- If you need to choose a tool to do something. After you find one, don't just use it: search for alternatives using: * crowded based developer alternatives sites like like alternativeto.net, stackshare.io * crowded based buisness alternatives sites like like g2.com, producthunt * Github topics based alternatives sites like libhunt.com, openbase.com, quine.sh
- If you need to choose a tool to do something. After you find one, don't just use it: search for alternatives using: * crowded based developer alternatives sites like like alternativeto.net, stackshare.io * crowded based buisness alternatives sites like like g2.com, producthunt * Github topics based alternatives sites like libhunt.com, openbase.com, quine.sh
Something I rarely see anyone do is use the Refactoring features in their IDE.
Most IDEs understand the AST of the code you’re working with. Knowing the AST allows it to restructure that tree, based on some simple commands.
One command is rename. You can rename variables, functions, classes, files, you name it. Your IDE should update all your code automatically.
Extract and Inline are two other useful commands.
Learn the keyboard shortcuts and you’ll feel like a wizard. It’s honestly close to magic.
Most IDEs understand the AST of the code you’re working with. Knowing the AST allows it to restructure that tree, based on some simple commands.
One command is rename. You can rename variables, functions, classes, files, you name it. Your IDE should update all your code automatically.
Extract and Inline are two other useful commands.
Learn the keyboard shortcuts and you’ll feel like a wizard. It’s honestly close to magic.
> Your IDE should update all your code automatically.
I don't usually like this when it's done without per-instance confirmation. Names are an important part of how information moves between parts of a program for those working on it, and making sure the names still make sense in all the contexts where they're used is an important part of that.
I don't usually like this when it's done without per-instance confirmation. Names are an important part of how information moves between parts of a program for those working on it, and making sure the names still make sense in all the contexts where they're used is an important part of that.
Learn your development environment (IDE, or text editor). It may contain features that will make your life easier, both during development and during debugging.
[deleted]
If some tricky manual process can't be automated, we can still write a script for it: just have it show prompts, like 'Toggle the widget, then press ENTER to continue'.
This way, we have a form of 'interactive documentation' telling what to do. We can even put branching (e.g. 'Did you get an 404 error? y/N'), and automate some of the in-between steps to make things less tedious.
This way, we have a form of 'interactive documentation' telling what to do. We can even put branching (e.g. 'Did you get an 404 error? y/N'), and automate some of the in-between steps to make things less tedious.
If you work with numerical algorithms, understanding the architecture you’re running on can make a huge difference to performance.
I once saw a colleague achieve something like a 30% speed-up just by swapping the inner and outer loops in an algorithm that worked on largish 2D arrays (matrices), just because the new nesting better matched the memory layout of the array and so made the hardware caches more effective.
I once saw a colleague achieve something like a 30% speed-up just by swapping the inner and outer loops in an algorithm that worked on largish 2D arrays (matrices), just because the new nesting better matched the memory layout of the array and so made the hardware caches more effective.
Take notes of what you've tried/what your results were when debugging. Simple, but massively effective for eliminating duplicate work.
That's a massive game-changer, I agree. I really like the approach described by my former colleague: https://softwarephilosopher.com/2022/03/07/personal-worklog/ but I haven't manage to fully implement it
There are many ways to do it, but almost anything is massively better than nothing. There are few things more wasteful in a long debugging session than running down a long, unfruitful debug path multiple times because you weren't sure you checked that hypothesis before. If you keep a log, you'll KNOW, or at least be able to check.
Not really clever, but having a tight iteration loop for whatever you're working on. This can look like creating custom scripts to automate commands you would need to input each time you iterate on something, or setting up an environment or UI to help with the development process. It can be a pain to put in the upfront work but it tends to pay in dividends down the line.
I remember being very confused when I saw !!x on a C source code. It's a shorthand for (x != 0 ? 1:0), to return error codes.
And then almost everything from JavaScript. Including
And then almost everything from JavaScript. Including
function setPos(horizontal,value){
this.position.edit()["set"+(horizontal?"X":"Y")](value).commit();
}Most of the programming tasks do not need optimization for time or memory. Instead optimize for readability of code.
Also, write tests.
Also, write tests.
If you're having trouble naming something, don't just name it `x` or `a` if this is something you're going to have to maintain. Instead, it's a red flag that your abstractions are wrong. It's often worth taking some time to figure out if there's a better structure.
I think I agree with this, but the initial "if" is definitely doing some of the work and could be unreliable.
Names like "x" and "a" and whatnot are just fine if what you're doing is math. You shouldn't feel like you need a "better" name than "x" for the input to the fast inverse square root function.
But if the names aren't feeling quite right, then (as you say) absolutely stop and consider why and consider picking something better.
Names like "x" and "a" and whatnot are just fine if what you're doing is math. You shouldn't feel like you need a "better" name than "x" for the input to the fast inverse square root function.
But if the names aren't feeling quite right, then (as you say) absolutely stop and consider why and consider picking something better.
Not exactly a programming trick, but adjacent enough.
Learn your tools. Learn the features of your IDE. Learn the flags and know and understand what your makefile or runner is doing.
So many times I've asked people that and they absolutely have no idea except, 'I run make' or 'I run grunt/gulp'
Learn your tools. Learn the features of your IDE. Learn the flags and know and understand what your makefile or runner is doing.
So many times I've asked people that and they absolutely have no idea except, 'I run make' or 'I run grunt/gulp'
The XOR swap trick is just a trick; it shouldn't be used in practice. Modern architectures can do a register swap almost for free, compared to three XORs. The biggest problem is when your two values are equal: they'll get zeroed out and good luck tracking that down!
That’s not correct: Equal value isn’t an issue. Having both variables point to the same memory is.
Oops, my bad! I knew it was something to do with equality, but I forgot the specifics… Another good reason not to rely on tricks, I suppose!
Agreed. Had to think about that one too a moment, but something felt wrong. XOR is bitwise, so if your statement was true, it would mean that two 1 bits or two 0 bits would both end up producing a 0 bit result. Clearly that would make the whole thing pretty useless :)
As I understand it (... haven't tested recently) you're right that it's not usually a win on modern architectures, and the compiler could do it wherever there's enough register pressure that it might actually help (if anywhere). It's still neat, but I don't think there's any reason to be writing it unless you are on a particularly constrained system with a limited compiler.
I assume you're being downvoted for your slightly incorrect caveat.
I assume you're being downvoted for your slightly incorrect caveat.
> The biggest problem is when your two values are equal: they'll get zeroed out and good luck tracking that down!
(a,b) = (5,5)
a = a ^ b # a = 0, b = 5
b = a ^ b # a = 0, b = 5
a = a ^ b # a = 5, b = 5
Hmm?Don't optimise until it works.
If working in a team, create a policy document that everyone signs off on. Don't sacrifice quality for speed when it comes to policy.
This ensures that code is transferable between team members without too much noise.
Work out solution from the end result. Imageine how end result should be (library API, UI components and interaction) and figure out how reduce it to building blocks you have (framework entities, text, functions-data, etc)
Writers say that the secret to writing is rewriting.
The same is true for programming.
The same is true for programming.
Set an alias for `git push` for `git pish` not strictly programming though!
Along the same lines I still use a text expander shortcut for for loops!
Along the same lines I still use a text expander shortcut for for loops!
Have a variable named 'toreturn'.
if you use Jira/bitbucket, you can prefix the branche names and git commit messages with the Jira ticket number.
This way git blame can quickly give you the the context of the ticket.
Get someone else to do it or do it yourself, pick as appropriate.
generics in typescript
seriously, generics in typescript is godsent.
it's great to be able to code in javascript while having a very supportive vscode intellisense
seriously, generics in typescript is godsent.
it's great to be able to code in javascript while having a very supportive vscode intellisense
Generic Utility Types are beautiful!!
https://www.typescriptlang.org/docs/handbook/utility-types.h...
I miss working with TS, it's just so much superior to old JS.
I miss working with TS, it's just so much superior to old JS.
use of linters is godsent too, eslint for javascript fixes a lot of compile time problems which avoids runtime problems
console.log({ variable }) when debugging
Write tests.
One word: awk.
Not a technical trick but this helps immensely when designing an application: always start with good problem decomposition and draft the technical components and their interfaces derived directly from that.
What I realized over my career is that almost always the software architecture quality just follows how well the problem was decomposed initially. If that decomposing processing is done well, getting to good technical and visual interfaces for the application is a straightforward process.
Let me give a practical example of good decomposition and translation to technical interface.
Let's say you want to develop an application to manage a parking lot. Start by listing what happens in the parking lot, thinking in a story format (user stories are not a coincidence):
- A car enters, the machine generates a code and prints into a ticket, the driver picks the ticket up
- Then the driver searches a suitable spot and parks the car
- Afterwards, the driver inserts the ticket in a machine that reads the code, calculates the price and ask for payment
- The driver pays, the machine returns the ticket and the driver proceed to exit
- At the exit, the driver inserts the ticket on another machine which validates if it was paid before opening the boom pole
Now, let's draft the components and their interface for that. Tip: the best names are already contained on that story. We need to: 1. Generate ticket code 2. Calculate price for ticket 3. Request ticket payment 4. Validate ticket
We can easily translate those to something similar to this:
TicketManager.generateTicket() => ticketCode
TicketManager.calculateTicketPrice(ticketCode) => float
TicketManager.requestTicketPayment(ticketCode) => (integration with the machine)
TicketManager.validateTicket(ticketCode) => boolean
Here is a good start point, you can decide to put those methods and their implementation into specific components depending on how complex those are. Yes, everything is included in that `TicketManager` but this is already a nice draft which can be improved upon.
My point here is that it is that simple. I use this approach both for boring simple and extremely complex use cases and it works wonderfully to keep things simple and connected with the domain. Just use the language that describes what is wanted from the automation, and use that in the final implementation directly.
There are multiple edge cases and scenarios to deal with within a problem domain, but the skeleton of your program is there and unless the concept of the problem changes, those won't go away soon.
This might sound obvious and dumb for some people but I stopped counting how many meetings I participated where people spoke in natural language what the program must do and in the very next moment that is lost completely by someone trying to translate to code using other terms and structure.
Our language already gives us what we want: verbs and nouns describe actions, capabilities we want to automate with the help of a computer program. Those same verbs and nouns should become function names within the software.
It is that simple, people overcomplicate things too much.
P.S.: Can't say for sure if this works nicely for other types of domains (scientific, research), but I would assume that it does. We humans create concepts using our natural language all the time. Writing software that represents those concepts is what programming is all about.
What I realized over my career is that almost always the software architecture quality just follows how well the problem was decomposed initially. If that decomposing processing is done well, getting to good technical and visual interfaces for the application is a straightforward process.
Let me give a practical example of good decomposition and translation to technical interface.
Let's say you want to develop an application to manage a parking lot. Start by listing what happens in the parking lot, thinking in a story format (user stories are not a coincidence):
- A car enters, the machine generates a code and prints into a ticket, the driver picks the ticket up
- Then the driver searches a suitable spot and parks the car
- Afterwards, the driver inserts the ticket in a machine that reads the code, calculates the price and ask for payment
- The driver pays, the machine returns the ticket and the driver proceed to exit
- At the exit, the driver inserts the ticket on another machine which validates if it was paid before opening the boom pole
Now, let's draft the components and their interface for that. Tip: the best names are already contained on that story. We need to: 1. Generate ticket code 2. Calculate price for ticket 3. Request ticket payment 4. Validate ticket
We can easily translate those to something similar to this:
TicketManager.generateTicket() => ticketCode
TicketManager.calculateTicketPrice(ticketCode) => float
TicketManager.requestTicketPayment(ticketCode) => (integration with the machine)
TicketManager.validateTicket(ticketCode) => boolean
Here is a good start point, you can decide to put those methods and their implementation into specific components depending on how complex those are. Yes, everything is included in that `TicketManager` but this is already a nice draft which can be improved upon.
My point here is that it is that simple. I use this approach both for boring simple and extremely complex use cases and it works wonderfully to keep things simple and connected with the domain. Just use the language that describes what is wanted from the automation, and use that in the final implementation directly.
There are multiple edge cases and scenarios to deal with within a problem domain, but the skeleton of your program is there and unless the concept of the problem changes, those won't go away soon.
This might sound obvious and dumb for some people but I stopped counting how many meetings I participated where people spoke in natural language what the program must do and in the very next moment that is lost completely by someone trying to translate to code using other terms and structure.
Our language already gives us what we want: verbs and nouns describe actions, capabilities we want to automate with the help of a computer program. Those same verbs and nouns should become function names within the software.
It is that simple, people overcomplicate things too much.
P.S.: Can't say for sure if this works nicely for other types of domains (scientific, research), but I would assume that it does. We humans create concepts using our natural language all the time. Writing software that represents those concepts is what programming is all about.
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.