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.
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.
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.
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.
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.
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.htmlSo 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.
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.
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.
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.
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)
// 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 alice@example.com
// 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 alice@example.com
// 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.
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.
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.
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!
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...
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.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.
Being jealous of keeping good Separation of Concerns comes to mind strongly regarding to this.
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!
- 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.
* 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/ :)
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.
* 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.
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 = case2
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.
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.
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;
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?"
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 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.
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
unicorn
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.
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
https://en.m.wikipedia.org/wiki/The_Power_of_10:_Rules_for_D...
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.
Even with modern IDEs this one neat trick saves a ton of annoyned confusion and swearing.
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.
* 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.
(2) Model based design (less time spent on less value-added concerns...plus, capturing even more benefit from (1))
- 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
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.
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.
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.
And then almost everything from JavaScript. Including
function setPos(horizontal,value){
this.position.edit()["set"+(horizontal?"X":"Y")](value).commit();
}
Also, write tests.
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 same is true for programming.
Along the same lines I still use a text expander shortcut for for loops!
seriously, generics in typescript is godsent.
it's great to be able to code in javascript while having a very supportive vscode intellisense
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.