Why is it so hard to see code from 5 minutes ago?
web.eecs.utk.edu
web.eecs.utk.edu
This is relevant to writing fiction or non-fiction as well.
For example, I didn't like this, so I commented out:
>though his companions jogged just behind him, he would have barely seen them at all had he look. They ran, with their heads down, attentive only to their feet slapping the muddy earth, tracking the path they followed, their breath ragged.
and wrote this, using the commented-out text as a reference:
The fog was now so dense that he could see nothing ahead of him. He ran with his head down, breath ragged, attentive only to his feet slapping the muddy earth, tracking their path. His companions followed close behind.
print "foo"
=pod
print "won't run"
=cut
Ruby basically copied it, although I don't think these have documentation uses: puts "foo"
=begin
puts "won't run"
=end
When I get to choose, I prefer sticking to // in C code so that I can use /* ... */ for hiding large blocks of code . . . although #if false ... #endif work too.While this isn’t a language construct (a comment is a comment after all), Visual Studio can key off these stylistic differences to show code comments vs commented code differently. I find it to be a neat trick to improve code readability.
I've never touched C#. But that seems like an approach applicable pretty generally. I like it!
There’s a StyleCop rule to enforce this as well:
https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/ma...
https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/ma...
StyleCop is used for analyzing code style in C# code.
Specifically:
> If the comment is being used to comment out a line of code, begin the comment with four forward slashes rather than two.
StyleCop relies on this to tell the difference between commented code and code comments. If you use ReSharper, it can do some nifty transformation to strike these out in the IDE which is kinda cool too.
I’m a big fan of StyleCop to enforce consistent code styles. (Even if it’s not always what I may aesthetically prefer.)
version (all)
{
... new version ...
}
else
{
... old version ...
}
and change `all` to `none` to try the other branch. #if 1
....new code...
#else
....old code...
#endif
Esp. for tricky stuff, it's then possible to just flip the 1/0 to easily test/compare/profile the new/old code.Some IDE's will even work out which block of code to colour and which to dim.
You can just use a life if-else, most compilers will optimize the test-on-a-constant away. Even when the code is kept in, I doubt it will have a major performance impact during testing in most cases.
#if !defined(this_works_but_it_suck_memory_too_hard)
....
#else
....
#endif
That should give me a clue why I "disabled" it when I come back to it 5 minutes later. //*
old_code();
/*/
new_code();
//*/
And remove the first slash from the first line to switch between blocks. // FIXME try new way mm/dd/yy
if(1)
{
... new way ...
}
else
{
... old way ...
}(Of course, if there's a stray /* ... */ comment already in the area to be commented out, this will fail. But an editor with appropriate syntax colouring can make that immediately apparent.)
I think the space looks nicer for text comments, but when I select a block of code and hit Ctrl+/ to comment it out, my IDE does not add a space. So it works out very conveniently for me.
I do this all the time in Emacs, usually to see two parts of the file that normally are too far apart to be visible at the same time. Collapsible sections can do similar, but to me it's quicker, more natural and more flexible to just split the window.
Doing this too early feels wrong; like I’m prematurely committing to a division of responsibility that’s going to subtly drive me to a bad design.
At first I gasped at your practice, because I never do that, but I see the point now. Mastery of language.
Yes, there is a shift in point of view between the two versions. But the story is mostly written in third-person limited, and so my first version deviated from that.
But I rewrite everything every time I read it, so who knows what will happen tomorrow.
Something is amiss here.
It's a tradeoff. I don't think either side of the tradeoff is insane.
That still doesn't give you easy access to the state of the code _halfway through the rewrite_, which is what the article mentions.
As an experiment recently I've taken to keeping snapshots of active work areas using rsync every minute that there are changes present - an automated full-tree version of "keeping copies of the file" that works for everything (images, word processor docs, ...) not just my code editor, avoids an accidental change when doing undo-redo "breaking" the redo buffer, and it survives explicit file closes which undo/redo buffers usually do not. There are two significant caveats: it only works at all if I save regularly while tinkering (or the editor auto-saves regularly), and it doesn't work for files mmap-ed or otherwise open-locked by the editing process. Not terribly efficient IO wise, and still wouldn't be with the improvements I know I could make but haven't got around to, but it has proven helpful at times and takes very little space for several days of snapshots most of the time (identical versions of the same file in each snapshot are hard-linked not duplicated).
When I'm satisfied that DataContext2 does everything that DataContext does, I delete old one and rename DataContext2 => DataContext.
The risk here, is that sometimes it's not possible to migrate everything to the new version and you end up with multiple old versions lying around, i.e. technical debt.
If you're using a language with strong typing and good refactoring support (right now that's C#/Visual Studio for me), the eventual rename from DataContext2 to DataContext is a non-event.
Use version control, do lots of small of commits, use diff. Have a fast edit, build, test/run cycle. If you don't have it then spend the time setting it up.
The point is that data is never destroyed, and this is one of the many causes for the unflagging preference for tangible records over ELNs.
FWIW, I also sometimes work this way; however I delete the commented out block when I'm finished. This is also a pet peeve of mine, the only time commented out blocks of code are acceptable is if the obvious way to write something introduces a bug or ignores an edge case. In that case, I also leave a note explaining not to refactor this section and explain why.
But especially while refactoring or fixing certain issues, I really find it helpful to have the prior version still present to compare against. If I have to commit while that commented code is still present, invariably my last commit message is along the lines of "removed commented code" or "cleaned comments for function XYZ". Especially since for these cases I also typically end up writing a comment once finished anyway. If there was something tricky enough happening that I was comparing against older code, likely there is something confusing enough happening that whoever is in the file next could also use some additional explanation that can only really be written once finished.
If you don't think of a commit message, you'll never be able to find this version again. If you think up a message every 2 minutes, you'll quickly find you spend more time thinking of commit messages than writing code.
I wish there was some kind of auto generated commit message. Things like:
"Added function xyz()" or "Adjusted constant FOO to 27" or "Made lots of changes in file a.c,, b.c and c.c".
These could be auto generated, and then commits could happen in the background every time the code is compiled.
It would be nice for git to have some kind of "commit of commits" which allows a hierarchical representation of commits. Ie. the "Add new printscreen feature" could have as subcommits "Create print renderer" and "Hook up print UI".
> "Added function xyz()" or "Adjusted constant FOO to 27" or "Made lots of changes in file a.c,, b.c and c.c".
I would argue that these are not good commit messages as they do not add information not already provided by the diff itself. Commit messages should communicate the intended effect of a change that has been made, rather than being a lossy compression of a sequence of keystrokes.
These are usually called feature branches.
> "Added function xyz()" or "Adjusted constant FOO to 27" or "Made lots of changes in file a.c,, b.c and c.c".
These are very poor commit messages that don't add anything of value; any diff viewer will tell you the same immediately, so you might as well leave it blank.
> Commiting frequently also has a substantial cost of needing to think of a commit message.
In larger, private feature branches I'll use loads of "asdf" commits that I will later squash together. These are my "ok, so far so good" points to make figuring out where I broke something easier.
You can edit the history later when it's required by somebody else working on the code.
Or you can just not care. 99% of the value of your git repo should be in the most recent commit.
- Stub in [classes/models/etc] for checkout flow
- Add basic test coverage
- Fix my API for easier testing
- Decouple foo from bar in new flow
- New checkout flow mostly tested
- New flow UI cleanup and add comments
- Fix nasty [N+1/O(n^2)/etc] performance bug in new flow
- New flow feedback from acceptance testing
- New flow ready to merge
- [And often enough/honestly] "WIP to share with..." or just
"WIP" for work in progress, "Fixing bug in" or whatever reality there was.
Going back at `git blame` etc 5 years later i can see from the branch names linked to the commit the why it was added and from the commit messages I can see something of at what point in the mental processing of designing/implementing that exact line made it it.“Added unit tests and stub out api” (50 files) vs “fix bug when files are added too quickly” (1 file, 3 lines)
I typically have it open beside my IDE so I can keep an eye on the progress in the file tree view. I can drag-select a set of lines, right-click and stage them, when I'm basically locking those in, then keep going.
It also auto-fetches every minute or so, so I have great visibility on what coworkers are doing in the commit graph. Lets me react to what they do pretty quickly, and I can implicitly review what they do and I can poke them if I notice something weird.
Here's an example of a similar update; would you say you like the second version more?
"Halting, stuttering, his words slurred, his eyes watery, his knees trembling, he thrust forth the knife, and with a cry the blow was struck-"
vs.
"His knees trembled, his eyes were watery, and spoke in a steady stream of unintelligible nonsense. With a cry, he thrust forth the knife, and the blow was struck."
This is the comment I came here for. (see what I did there?)
I also sometimes keep multiple versions of the same line in comments, such as when I'm trying to a tricky regex work. For example, I might have code that looks like:
# x = regex1 # Original line
# x = regex2 # Doesn't work
# x = regex3 # Nope
# x = regex4 # Also nope
x = regex5 # Currently testing this
That gives me the way to see which approaches I've tried while solving the problem, and ensures I don't actually use the same exact regex twice.When I get a working solution, I comment out all but the line that works and commit that.
Programming is not black and white, there is no right or wrong way to do it. OP discovered a problem they had and wrote a solution for it, believing that others might need to solve the same problem. Clearly they were correct based on plenty of others chiming in with their own anecdotes, or tools they've discovered that help alleviate the same issue in other ways.
If you've never had to UNDO/REDO a block of code to revisit a previous state before moving forward again, kudos. But suggesting those that do are somehow going about programming in the wrong way is not constructive.
Well, open minded people can try other ways. Mine is sitting back and thinking instead of racing the keyboard to try out stuff until it works. Both ways (as I compare to my exclusively younger colleagues) reap results in about the same timespan, but I hardly type more than the actual code I need to type while my colleagues write and delete books ('iterate fast') in that time. It both works, but I have several converts who now work like me and actually enjoy it more (not to mention far less chance of RSI).
Whatever works for you I would say.
What is a bad thing though is that it also has to do with the pressure put by WFH in some companies and sites like Upwork => a lot of 'managers' (and sorry, I cannot say anything positive about these people, but their number increased rapidly during Covid in my experience) measure productivity of an employee by keystrokes, screen changes and # of commits. Even though I finish more tasks in a day than most of my colleagues in our projects, according to those metrics, especially keystrokes and screen changes, it looks like I'm doing 'nothing'. Whatever.
Aaargh, that's the most quality destroying metric possible.
It really isn't that hard to know that through experience and a little bit of forethought.
We have a separate budget for "development" and "hardening" - these two are actually stages - one starts only after the other ends and that moment is already precisely scheduled. No QA team so far.
So we play this little game of delivering something, anything and allow ourselves to leave some details behind.
I know of multiple problems in my code which made it past review, because there's no incentive to block merging on grounds other than code style, and maybe some glaring issues.
My gut feeling is that this approach is more expensive.
Isn't that what coding is?
Bug fixes have a bit more trial and error to them obviously, but it's usually not reading entire code blocks.
If some function becomes too gordian I start working on a refactoring branch to fix it.
I also use 2+ monitors so I experience very little of the code deletion issues referenced here.
When I was an undergrad, I wrote code in a text editor without syntax highlighting, I debugged using log statements, and I manually tested every change by running my program manually with different inputs.
Of course those are all fixed by pretty basic things, but you would be surprised how many programmers literally don’t know how to use the tools they have, or are unaware of them. And some tools are downright hard to grok- git is definitely one of those.
Isn’t that just called “experience”? It’s understandable that junior engineers don’t know these tools exists, but most will learn them after a few years.
If you’re curious and interested enough, you’ll wonder if there are better ways to do things. Or talk to more experienced people and ask them how they do things.
I don’t think this is a “problem”, it’s just part of the process of becoming an experienced engineer.
By default, Emacs has a linear undo structure, while still allowing you to never lose history.
The undo-tree terrifies me.
The source for undo-tree contains documentation which very effectively describes the way the library works with examples and comparisons with how Emacs does things by default: https://gitlab.com/tsc25/undo-tree/-/blob/master/undo-tree.e...
More generally, for anyone new to Emacs and struggling as I once did, I can't recommend strongly enough that you learn how to use the help system (C-h C-h) and the built-in manual (M-x info). Emacs can teach you a great deal about itself, and these are the ways it does so.
By default, you can select any block of text and the undo command will cycle through the changes only from that region of your buffer.
does it understand syntax boundaries like curly braces / functions? ('undo within function' would be key). or just line #?
For eg, see https://www.johndcook.com/blog/2017/08/09/selecting-things-i...
EDIT: Or are you programming in APL?
Another vanilla feature I sometimes use to keep track of old code is the "kill ring". Just cut the old code and it will be available in there later if needed.
Not something I use often but as a last ditch effort to find code that I've spent hours or days on and subsequently lost to a git mistake, or reverted thinking I was going to use a different approach, it's worth the price of entry for the JetBrains tools alone.
As for my personal projects, I recently realized that using leveldb wasn't going to cut it for me right before I finished the database storage feature. I still finished it to create a commit that didn't break the build and then worked on switching to rocksdb. If in the future I decide to switch back to leveldb, it's always there.
I do this kinda stuff all the time in my digital art practice. Duplicate a layer, hide the duplicate, start making changes. Does it work better? Awesome, delete the duplicate. Did my idea not improve it after all? Cool, delete the new version and rename the duplicate back.
It's simple and reliable and builds easily on existing structures.
And I don’t just mean “git cli is a bad ux” (though I mean that also), but that the whole world of version control is poorly serving rapid prototyping and other exploratory flows.
It’s pretty likely aliases could serve some of this by wrapping a lot of fast paced idea checkpoints into a set of git actions. But it still would be disruptive for eg any intermediary file system side effects if it’s not faster than whatever watcher you have running.
because of this comment I just decided to create a keyboard shortcut for commits to be alt+cc so now it's a little easier, I still need to type a commit message and accept save and stage, but at least it's all doable from the keyboard.
My version is super incomplete because I kind of lost steam when I couldn't figure out a really great reason why someone should use this, but hey, maybe it's worth another try: https://github.com/nicholaslyang/codemkin
Until this point I thought that was just a bad practice on my end, so never saw the need/opportunity for something like a state slider.
Honestly never thought other people were using undo/redo like this.
I understand how to do WIP commits etc, but its helpful to do a rewind/fast-forward that includes all the character changes in-between the logical “blocks” of diffs. It often helps me recapture approaches to problems that I’ve already attempted.
It can and should be done automatically by the IDE to enhance developper productivity by getting out of its mind. There is no reason why I should be thinking about that, isn’t that all the point of using IDE and tools to develop ? Seems like using git for that is still re-inventing the wheel
The only things to watch out for with local history in these IDEs is that the snapshots are deleted after five days by default, and all are wiped if you need to delete your IDE cache for any reason.
Thanks for mentioning it - absolutely perfect!
In vim you can do:
:earlier 5m
to see the code you had 5 minutes ago.> (1) If you go to a prior state and then make a new change, you can no longer redo and all those changes are lost.
Vim makes a tree, so that doesn't happen with it. When you undo and make a new change, you're just making a new branch. u/Ctrl-r goes from leaf to trunk, but with g-/g+ or :earlier/:later you can walk the whole tree.
Emacs similarly doesn't lose changes in this scenario, but instead of making a tree, it has a ring and undo is an action that can be undone.
> (2) You can not see a side-by-side comparison of the previous version and the latest version.
It's probably not complicated to make a macro or function where you go to a previous version, copy the buffer, paste it in a new one on a new window, return the original buffer to the state you were in, and diff the windows for that side-by-side comparison.
A command like so suffices:
:earlier 5m | %y | later 5m | diffthis | vnew | put | 1d | diffthis
> (3) There is no visual indicator of where you are are in your undo/redo history.Not by default, no. :undolist provides some info, though, and the vimscript undotree() function probably provides all the state of the undotree. There might be plugins that somehow present this info in the interface.
> (5) I have found many actions in code editors that do not get added to the undo stack (e.g., changing a debugger option), which caused me problems in the midst of an annoying bug.
In vim, only stuff that changes the buffer is added to the undo tree. Navigation or changes of the state of the editor (e.g. editor options or vimscript variables) aren't added. Are there really editors where adding actions that aren't changes make sense?
> (6) There is no indication of what steps were "big" or how long ago they happened.
That's also true in vim it seems, at least how "big" they where. Each step does have a timestamp, though.
> (6) It is tedious to backtrack one small step at a time.
You can backtrack by however many steps you want. :earlier also supports specifying by seconds, minutes, hours, days, or file writes.
Most cool of all is that vim can persist the undo history. I don't know how common that is among editors, but in vim you can go to a file you haven't opened in years and undo it all the way to its beginning.
It automatically commits after every change since the IDE was opened, and can easily be diffed and reverted as needed. Reverting saves another entry, so you can revert back to the future as well.
1. Realize I need to check how my new code looked like 5 min back. 2. Copy full file into clipboard, undo by a few steps 3. Do a temp commit on GitHub desktop 4. Paste the new code back and look at the diff on GitHub desktop, and if needed undo the temp commit from above.
Is this more clicks than the vin shortcut? Sure. But I don't have to go learn the internal tree structure representation of my text editor and spend my time in that universe. I have found a way to do what I need to with the tools I have in hand.
This is the same way how the majority of finance runs on excel when they could do better with better programing. In the end whether you get the job done in fields where you're not billed by the second is not dependent on how well you use your tools necessarily.
The other analogy is cars - for most people it's something that gets them places, for others it's a way of life if not at least a more involved proposition.
But if code is your way of life, which it is for many people here, it is beneficial to learn the tools that allow you to do it easier. If someone can see how their code was 5 minutes ago in a few keystrokes, they’re going to take advantage of that significantly more frequently than someone using your cumbersome method.
But for me, every little thing I learn in vim pays off... a thousand times? Macros alone have probably saved me a cumulative few hundred hours. And with every other little thing I learn, compounds with this effect. I write macros much better than I did five years ago, but I still surely have tons of stuff to learn. No IDE will ever compete because vim is just four keystrokes away at any time, no matter where my terminal is.
Personally I have found Vim more approachable than IDEs actually. It doesn't throw all those overloaded toolbars at you according to some one-size-fits-all principle. For me it went so far that last year I actually did a Java project in Vim instead of an IDE for the first time, because the latter had grown to be so frustrating to use.
Also, if you go cold turkey, you'll be productive in less than a month, and arrive at a good workflow that fits you in less than six months, flow that you will improve over the years in amazing ways.
Likewise, if you have any recommendations for your preferred workflow I'm sure some will find it useful. Hopefully those who don't use your preferred $EDITOR don't then rush to complain that not everyone uses $EDITOR. ;)
> There might be plugins that somehow present this info in the interface.
The undotree vim plugin [1] does this, and gives both the file at the time as well as a diff of what changed.
I was going to mention this plugin. I haven't fully mastered quickly jumping around in it, but it's one of those things that when it's useful, it's VERY useful.
nnoremap u g-
nnoremap <C-r> g+
It takes a little bit of getting use to, but it takes care of most instance where I need a previous edit from a minute ago.
Adobe Photoshop is one. Selection changes don't modify the document at all, but they are part of the undo stack. That's because selecting a part of an image isn't as trivial as selecting text.
Sometimes I wish the current selection could be automatically saved in the file too so you could start where you left off if you were halfway through making a complicated selection (I know you can manually save selections, but that's an added step).
Emacs default undo system is really, really bad. Like worse than the "standard" one. But after installing the emacs undotree it becomes sane.
I wouldn't say that it's worse than the standard, though. Unwieldy as it may be, I like that I can just keep on pressing C-/ and eventually get to the state I want without risk that I may have lost it because I accidentally made a change after a series of undos.
> Emacs documentation recommends using C-f (forward-character) to switch to redo mode, but in my opinion attaching side effect to movement is awful.
I use C-g (keyboard-quit) for that, which is the general let's-not-do-this-anymore keybinding. I think the documentation mentions C-f as an example keybinding that does the needful without much thought on being the "best" keybinding for it.
Are you aware of the `undo-only` (and `undo-redo`) functions? They make it a breeze to navigate even the most complex undo history.
(undo-redo) doesn't seem to be defined in stock Emacs, but undo-tree defines (undo-tree-redo), which does the needful.
My comment was more about lamenting the defaults in comparison with vim's, but it's true that it doesn't take much configuration to make it much better.
I absolutely love this feature. I regularly open a file and undo/redo to figure out where I was editing last.
I can't imagine it'd be that complicated to add it though, if you really want it. I think it'd just be a matter of hooking to the undo function and maintaining a list of timestamps for each item added to the undo list, then adding a command that lets you specify the number of minutes and figures out the correct undo item it should go back to by using that list of timestamps.
There is also a command history, and jumplist (movement cursor position history) for things that don't go in the diff tree.
I only use it when experimenting with different approaches.
Mostly I only use the "Local Changes" tab in the Git panel that shows the diff compared to the last commit, or "Compare with branch" to see the overall changes relative to the base branch.
Unclear why this isn't baked into the OS nowadays. It's not like we don't have enough disk space ;)
I figure you could adapt that to something a bit more restrained that you could use on an ongoing basis to have a "Local History"-style feature for your whole OS :).
Searching now, it looks like this is part of NetApp ONTAP, and managed by a snapshot policy.
Maybe a little clunky, but works well for me.
Doesn't help with IDE settings and such, but that's such a niche situation that it doesn't matter at all for me.
Before anyone comments, git doesn’t cut it for this because no one commits after every -+1
EDIT: And of course I read the article after reading the comments.. Haha, VS Code plug-in in 3.. 2..
not super polished nor is it actively maintained but has promise
I use it with every project and it works great. Helped me out a bunch of times. Just need to remember to ignore the .history folder from git and other tools.
https://marketplace.visualstudio.com/items?itemName=Wattenbe...
Having said that, this approach only works if I know up front that I’m going to want to compare the code. Far more often is making a change and then realizing I want to see the old version. A history timeline would be extremely useful here.
Git stash can handle your use cases too, but commits are more powerful as they let you compose multiple pieces together
Branches rightly became a bit of a dirty word over the last decade. In a fast moving collaborative codebase the pattern you really want to avoid is using a long running feature branch — one that remains uncommitted to trunk for ever more uncomfortable periods of time.
Branches themselves — the VCS technology part — are incredibly useful when used fluently.
cp code.d code.old
if I'm going to make some trial changes :-/Are most modern developers struggling to write code without this? If so, there is a fundamental problem that has nothing to do with levels of undo.
make change => see result => cmd-z 1 time to fix result OR commit and keep coding
oops, it wasn't really fixed. cmd-z 3 times and see if that works
I know it's wrong, but what's right?
Nah, it's not wrong. You're testing your code changes live.
(I know you're joking)
The closest imaginable thing I might do is wrapping some block of code in an if(false) {..} or #ifdef it out, when I'm refactoring and feeling unsure about my changes. But is that even the same thing that we're talking about here?
Can't imagine how anyone doesn't do this - unless there's just a race of God-programmers I've never encountered that write everything perfect the first go. But I don't think so. :-)
All versions controls have a way to compare, merge and revert historical changes in and out of the current working copy.
I do all of these. I’d use undo/redo when I want to look at something I /just/ did, meaning while in the process of writing something. The git gutter when I stumble upon some change I want to see what was there before. Or Sourcetree for a full diff.
But also, `git diff`.
On one hand it's nice that there are tools to support devs who get lost in their undo-redo history, on the other I feel like it's a matter of good habits to not even have this problem.
of course changing habits is hard, so maybe tooling is justified in this case. I'm just happy I don't have nother "history" type mental model to deal with.
Git stash can be helpful. Stash your "wrong direction" change, then apply just the good chunks from your stash.
Yes, git diffs help, but once I’ve passed a parameterized suite of unit tests, never needed to undo/redo. Maybe not perfect the first go, but only need one/two tries to working code. Then refactor for readability.
Consider, for example, a bug recently introduced during a refactor at my work:
The programmer optimized a conditional based on a regex by transforming it into a simple string compare. All the tests passed, code/branch coverage was good. Except that he missed that the regular expression tested case-insensitively, and our test suite didn't test upper and lower case scenarios.
This simple mistake outlines a few flaws:
- coverage was not robust, because it didn't take into consideration the branches inside the regex (which one could interpret as a form of macro expansion)
- the limited number of inputs used to test failed to capture the broader domain of possible input
- the test did not reflect a successful refactor
Obviously this is a fairly complicated example despite a pretty simple change, and several pieces had to fail in order for this change to fail. But it affirms that even basic changes to code aren't necessarily adequately covered by TDD.
Yes, the developer who forgot to test different letter cases made a mistake. Yes, regular expressions bring their own problems. But fundamentally, the result was that passing the tests did not affirm fitness of the change. Rather, it only proved a limited subset of conditions were error-free.
Effectively, tests are loaded with false negatives, so trusting them to identify problems should be done with a massive grain of salt.
If the developer had simply copy/pasted the code he changed and compared the two, then it's exceedingly more likely that he would have noticed that his code didn't capture the full breadth of conditions in the previous code.
A bug existed in the test suite, to start, and then a bug was introduced into the codebase. A human being looking at the two lines of code as it was rewritten probably would have noticed the regression. But even a code review missed it because it was a fundamentally small change among the other, more "make sure this looks good" code.
Personally, I very much value copy/paste/compare changes and never treat the test suite for anything other than "well we haven't broken anything in any exceedingly obvious ways." Maybe you're a superhuman programmer, but I'd lean more towards "you've probably added more bugs than you realize".
The caps vs. no caps issue would be easily caught using randomString() as the test case.
This is from an example I did just yesterday.
Let’s say you’re moving a piece of code by cut n paste. You cut it but in the middle of changing you copied something else, maybe accidentally. Undo and cut it again. Redo and it’s now back in your clipboard.
You could copy from the diff but you’d get extra tokens to clean up.
If I'm reading this correctly it means applying a large number of undos to get to a previous editor state, followed by an equally large number of redos to then get back to where you started.
I can't recall every doing something like this.
The only time I seem to use undo is when I genuinely make some sort of editing mistake.
One reason I know I don't subconsciously do this is because I never struggle to execute an undo action.
However, on the rare occasions I need to do a redo I tend to struggle with the short cut key and usually go to the menu instead.
That tells me I use undo much more than redo.
Sometimes it's easiest to hit Cmd+Z a few times to check out how something looked at an earlier stage of a change. And then it's easy to Redo everything back to the current state. Rewinding changes back can even jog your memory as to why you did something. It's fast, simple, and effective. What's so bad about that?
I think you simply don't understand the workflow... when and why people do this.
Generally, the idea of referring to an older version of code while working on a later version is good and useful, is it not?
This is just one way of doing this.
NetBeans has the same feature. I cranked it up to keep it for 30 days. Very handy. It's essentially an "automatic git" on every save.
I can't imagine it would be too crazy for IDE's to begin to add the same, per file at first but maybe also per project and then with saving some of those trees.
When I get thrown into an un-typed python codebase, my productivity grinds to a crawl, and I start having to do things like have a scratch pad with various versions to copy paste.
> I'll Ctrl-A and Ctrl-V this into a new tab before it gets too messy
I did this about 400 times today
I could see this being similarly useful in code.
One interesting feature might be to be able to select any two commits such that you could diff them in isolation.
I'll be typing some code and realise I need to check whether the imports at the top of the file are correct.
So I'll Cmd+Up (go to the top of the file) and see that yes, they are correct. Now I have to get back to where I was.
A quick Cmd+Z,Cmd+Shift+Z does the trick - Undo (takes me back to the code I was editing, and undoes some of it) and Redo to put it back to how it was. Works well, but would mess up this researcher's statistics.
(I absolutely do also use the undo stack for looking at the last few minutes of history too, that's really valuable)
cmd+-, if I remember correctly.
But, often times I have jumped around many times, or only scrolled. It doesn't work in these situations. Whereas `undo` `redo` reliably brings me back to the place of last input.
You can use `ctr + -` to also jump between documents. So if you’ve jumped to a function definition in another document the back shortcut can take you there.
In Emacs, you can often use C-x C-x for this, since many movement commands which moves far away, like beginning-of-buffer, sets the mark at where you moved from. So C-x C-x moves you back. If this does not help, i.e. you have moved several times, you can almost always get back by repeatedly doing C-u C-Space until you find yourself at the correct place again.
Seems a bit suspect the whole premise really. I don't really end up doing what this article outlines. I find a lot of programming studies are often done on what is immediately available... students, and I think this tends to not really be indicator of what experienced programmers do.
At least 10 years using it. What's interesting though is that such a feature would be off the radar for other editors and even the OP article.
That being said I don’t mind committing often. If I have a slew of small commits I squash them.
Since I will need to commit at some point, I rather do it incrementally.
By "diff view" I mean I can see all the changes I've done from the main branch.
I suppose I can stash my current code, reset to where I started somehow, and then unstash.
I've also toyed with the idea of having two repos on my disk.
https://github.com/zaboople/klonk
Note that in theory this can cause an exponential growth of the undo/redo stacks because it makes an upside-down copy of the future when you change the past; but in practice it's never been an issue.
I was thinking of adding a feature to navigate back to "last change-the-past", which wouldn't be hard.
It at least makes more sense than most science fiction time-travel plots...
I usually use these 3 approaches in IntelliJ (IJ):
1. When a line is changed, IJ will show some marker in the left gutter. If I hover my mouse, I can see the diff.
2. Sometimes I peek Local History.
3. Sometimes I commit with random name e.g. "bla" so I can refer to that commit from time to time. When I'm done, I squash all the blas into 1 proper commit.
I think the issue is that the undo/redo concept is inadequate. The author's solution of a persistent, read-only history seems like a step in the right direction, but that seems like it's much too slow of a workflow to beat out CTRL+Z/Y/Shift+Z, which is muscle memory for most.
Maybe we need something like a hyper lightweight git that can be controlled via keystrokes.
It's really disappointing how so many interfaces have been stagnant for so long. It sometimes feels like the only software difference between a computer from 2021 and 2001 is that the graphics and animations look nicer, but otherwise is just more of the same stuff.
I see that as a feature, not a bug. I’ve been saved countless times when I accidentally messed up my git or local state.
I see it as a product named Multiverse Editing. You start off coding in the prime universe. Each Undo places a node on the current universe's timeline. If you continue editing you are still on the prime timeline. But you can click on any node in the timeline to go back, and editing from there branches to a new universe with its own unique timeline. Each node represents either complete before and after code for the current timeline and a pointer to the root of one or more new universes. you can select any two or three nodes and perform a merge which results in another new universe branching from the source (left) node of the merge. A database will exist for each source file, each representing their own multiverse. Source control events would become nodes on the timelines such as branches becoming a new universe and merges collapsing universes together. Ideally the database would be in the SCP and not the editor, though that might get slow, nut that would preserve the complete edit history for a file. Each node would have metadata where the developer could add notes in MD format, etc.
This is the same way that whitespace and long variable names make code harder to read and reason about: simply because you can see less of it.
Presuming DVCS here; if you’re still on a centralized VCS this is surely Not Good:
So, you need an editor utility that automatically commits every save (and possibly autosaves and commiys every completed undo-list item)on a special temporary branch that gets deleted when you do a real commit, and if you go back to prior state on that branch it automatically does the same but starts a new temporary branch with the next item, deleting the whole temp tree on a real commit. Or maybe leave the temp branches around until manually cleaned up, but provide a UI to do that in an all-at-once sweep.
The obvious challenges seem to be managing temp-branch vs. “real” current branch state in a way that doesn't mess up or get messed up by other tools interacting with the repo (including ha sling things like pulls and other inbound changes to your “real” active branch), and generating non-noise automated commit messages. Advantages would be all the tools you have for VCS history can then be deployed to your edit history without any additional friction.
Low-level plumbing commands, if the VCS has them, can help with that, e. g. `git commit-tree`.
It will also just print the new commit hash without moving `HEAD`, which is why it may also be useful in dragonwriter’s use case.
Raymond Chen explains it way better than I do: https://devblogs.microsoft.com/oldnewthing/20190506-00/?p=10...
List of related blog entries: https://news.ycombinator.com/item?id=20620441
Having a slider/timeline does sound like an excellent idea. Also just being able to non-destructively go back to a previous state and have a peak without changing the files I'm working on right now would would really well for me I think.
Of course git is an answer to this, but I don't want to think about making a checkpoint.
Commit often and use reflog?
That being said, I don't think the reasons viven are honest: > When asked why they did this, they revealed they were trying to view some intermediate state of the code in the middle of a change.
Looking at some GitHub repos, with one single commit, I believe many devs are afraid someone could see all their trial and error attempts and judge them.
I oersonally am too lazy for the undo redo nonsense. Many of my final commits are mostly cleaning up the code. E.g. Refactoring so my code is like reading a story.
I don't have an issue with people thinking I have OCD or something similar. My bigger issue is if I ever have to touch my code again, to waste time on trying to understand it. Or even worse, someone else not understanding it and asking me for help.
This is certainly part of the reason I do it. Sometimes I'll start repos anew, without the "mistake" commits and branches, and add that version to my Github before I send the link to a potential employer.
https://git-scm.com/docs/git-merge
C-f squash
You can compress a series of commits into one with squash in git.
Clipboard manager allow you to store all clipboard history.
Thats not the best solution, but at least for me with it I able to copy some original and intermediate states of code few times and return to it anytime in the future.
* works well entirely through keyboard shortcuts (especially useful when editing code)
* the shortcuts are standard so you already know them and have them committed to memory
* works across editors
* high granularity of control over exactly which version of the old code to refer to
Undo trees seem like the best alternative, since it can be done strictly as an extension to convention undo/redo... a little HUD can let you see your current location in the tree and the normal keys can be used to navigate it, with the addition of one more shortcut to change branch.
The "separate window with panning control" suggested in the article seems awful to me.
> Memo is an operation-based version control system that tracks changes at the level of individual keystrokes and synchronizes branches in real time.
[1] https://github.com/atom-archive/xray [2] https://github.com/atom-archive/xray/tree/master/memo_core
So, it would fix this problem, and also enable real-time collaborative editing, had it been completed.
It ostensibly seems fine, updated today. https://github.com/atom/atom
[0]: https://www.autodesk.com/products/fusion-360/blog/wp-content...
Would love to be able to replay notes that I write with the pencil on the iPad.
I also feel it would enable a cool way of storytelling in which you can play with the way you write so readers get to experience the text in different ways. Imagine that as you are reading something all of a sudden the letters start getting bigger, or thicker, or star changing color. Anyway, just replaying handwritten notes would be nice.
I dont use ctr-z, Its surprising to me that people code this way, but I do tend to spend a lot of time thinking about how my data would flow through my current code set. It would be great if I could easily and almost cartoonishly visualize it. Anything like that out there? gdb is just to abrubt an harsh to use.
It's set to delete history after 7 days but can be changed to never. Haven't realy used it much last few years though so don't know if it's still there.
Idk I do think version control is probably a much better tool to sanely manage rapid workflow when working at scale on huge projects, I just think the problem is that there too many features and far to few people who actually take the time to learn how to use them effectively.
To prove my point, the awk tool is super powerful and solves many "quality of life" problems developers begrudgingly deal with on a daily basis over and over again.
Now lets see a raise of hands of people in here that can actually write an awk script one liner without referring to the manual?
We have all these wonderful tools but most of us are too spoiled by having a mouse and powerful editors that we don't ever learn how to use them to improve our performance as developers.
using vim w/ git-gutters plugin, which shows the state of a line (+-~), and so you can stage and unstage these hunks with mappings.
so maybe you unstage a whole hunk to see what its original state was, but then just do an undo to return it. this is technically many undos in one
there's some more conveniences here, but this is one that stands out regarding "many undos ago"
If operating on multiple files I can have the snippets in the same tab, or different tabs in Sublime.
https://marketplace.visualstudio.com/items?itemName=bee.git-...
We had considered ways to expand on this feature for educational purposes by adding audio to create a tutorial but never could allot resources for this.
Be right back, I'll go implement it right away.
a) git add and git diff --cache
b) :w and git diff (or git diff HEAD to compare a+b)
c) nmap <leader>d :w !diff -u % -<CR>
so that’s three diffs / four different stages.
If I need more I also don’t refrain to use sequences of temporary commits I rework with git reset master + git add -p and/or git rebase -i
So overall there’s no fear of early commitment to be had.
Why is that a problem? Just commit, as many times as you want, to a throwaway branch.
https://devblogs.microsoft.com/visualstudio/auto-history-ext...
This can vary by problem domain and language. I see some examples of regex below and I can agree with that, because regex is difficult to read.
Because you aren't using Vim: ":earlier 5m"
This seems... fine?
Ah hah. I'd like to use the code history slider plugin too. You've done well to identify a common problem many of us face.
Also for fun, consider having your scroll wheel traverse time instead of space [2].
[1] https://github.com/mbbill/undotree [2] https://xkcd.com/1806/
It is indeed difficult sometimes to understand our own code some time after it is written, but by following good practices (ideally functional, with mostly pure functions), it should be readable.
I will venture a (biased) guess that one of the biggest challenges to understanding the code was the mutation of objects in the process. This is the #1 reason why functional programming is superior. With FP, you can grok what a function does and then comfortably forget about how it does it. It takes input, it produces output. Period. As long as you agree with the transformation algorithm, you can reduce that complexity to a one-line description.
Even in non-FP-first languages, you can usually write pure functions. Some scenarios have enough performance demands that you must mutate things, but probably most situations can be approached with FP styling.
All this rewinding and undoing is a distraction from the real problem. The real problem is that "it smells". It smells like a level of complexity that should not be.
Not to be harsh, but I really think that this indicates some fundamental approach issue rather than a tooling problem.
If it cannot be written on paper (or tablet note with pen) as rough pseudocode... or even drawing boxes and arrows for the visually-inclined, then the problem is not understood. Take what is understood and codify it, pushing the complexity and the unknowns to the edges. At least that reduces the complexity.
One aid I do lean on heavily though is a REPL. Load the code and do some interactive work with it. Inspect the data, test some transformations, and then write code to do what works. That's my current approach to situations that I cannot immediately and accurately write in one pass.
Honestly, this sounds mostly like what you're chastising, except you're doing it outside of the main workflow.
This seems to be a fine example of that.
(Though I must admit that consternation about how often someone presses Ctrl-Z is a bit lower level than I’m used to seeing.)
Have you ever had to build an os kernel, a database, an mmorpg server, a realtime 3d game engine, a production compiler, jit, or garbage collector?
And sure, it may be long ago, but I have some code in a AAA game. Even C++ with hundreds (*edit) of files, we got along just fine without rewind.