Thank you for your donation!   Download the book!   Join the group!   Public source code repository
Showing posts with label advice. Show all posts
Showing posts with label advice. Show all posts

Grepping over history

In a series of posts for a future, forgetful me; today's topic is a way to search for changes in a file content over past git commits. Turns out it's not that easy, it's either a two step process; or, one gets laundry list of every occurrence of whatever is searched for that ever existed.

I opted for two step process; first search for commit IDs of interest, then get files in that commit:

  • git log --grep=<regexp> lists all commits with a message pattern,
  • git log -G <regexp> lists all commits with a content pattern,
  • git log -S <regexp> lists all commits with different count of content pattern (additions vs. deletions),
  • every search can be further restricted to a file (or folder), e.g. git log ... -- path/to/file/.
Now that commit IDs are filtered-out, one can get file by using:
git cat-file --textconv <commit-ID>:path/to/file;  --filters can be used instead of --textconv. I find it more convenient to just dump committed file into a temporary file, like so: git cat-file ... > temp.c, then open said file in a text editor. Every text editor nowadays is aware of external content change in opened files; so, to repeat search with another commit-ID one has to do as little as just dump into the same file. It's also convenient if temporary file has the same extension as committed file, that way text editor can do syntax highlighting, without having to be set manually.

For a laundry list of everything everywhere, one can use git grep <regexp> $(git rev-list --all); the problem is that it's very long, even if restricted to only one file both in grep and in rev-list, like so: git grep <regexp> $(git rev-list --all -- path/to/file) -- path/to/file.

Undoing git push

This is just a quick note on how to undo push already made to git repo. This will undo changes to your workspace, local and remote repo.

First, do reset:

git reset --hard HEAD~1

Instead of just resetting last commit (to previous HEAD~1 commit), you can reset e.g. last three by entering HEAD~3, or enter SHA-1 of last-good commit.

Then, force changes from local repo onto remote:

git push --force

Obviously, all changes made to repo after chosen commit will be lost, if they are not in some other branch.

Your colleagues will hate you if you screw their work. Take care!

Of style, and braces

I always subscribed to Allman style when programming in C; granted, before this project I never had any larger amount of code. Now I do have, and I'm finding that style to be getting in my way more and more.

You see, I do like to have empty lines in my code to separate small, logical chunks in a single block; usually, that means every few lines of code there is a separation. That way, code isn't all clumped together in one giant, incomprehensible block; it reads "light", and is much easier to understand.

Having intro and outro around every block, and code becomes more difficult to read, because it's visually more separated, and no matter how big your monitor is, it can only display so much. The thing is, I prefer to set my own separations, and the ones dictated by style I found useless, at best.

So, yesterday, I had a sort-of satori: what if I move braces around? Sure enough, there are already plenty of styles beside Allman's, you just need to pick one, and refactor code.

So, I went from Allman style:
if ( foo )
{
    bar();
    bar2();
}
else
{
    baz();
    baz2();
}

to one-true-brace-style, and found it okay-ish:
if ( foo ) {
    bar();
    bar2();
} else {
    baz();
    baz2();
}

That left me wanting more, so I decided to try to move outro braces out of sight as well:
if ( foo ) {
    bar();
    
bar2(); }
else {
    baz();
    
baz2(); }

Surprisingly, I found it to be readable, understandable code. As the saying goes, beauty is in the eye of a beholder; so I won't claim it's beautifully styled code, it certainly takes time to start appreciating it. Still, if you can ignore braces, it's reasonably formatted.

Gains are significant, too. With just one simple trick, I went from almost 10k SLOC down to slightly over 8k; difference is about 1500 SLOC, or about 15%. But more importantly than just numbers, code is so much easier to read, and understand, because related functions are shorter, and thus much less visually apart.

As everything in life, this style too has it's own fair share of downsides. First, and foremost, braces aren't visual fences of code blocks anymore; they are now just for compiler. If you have any of those fancy code editors/IDEs which can color braces, and maybe even indicate block between them, that would be major help. But even so, it's PITA to have to click at code element you're interested in, just to check if it really belongs to block you think it should.

Another issue is that you now relay on proper indentation to convey intended code blocks, formatting. Python was designed that way from the very beginning, but in C it takes time to get used to it. Arguably, that's not really an issue, most programmers do indent their code properly anyway.

Which brings me to final downside: it's possible to encounter code where indentation does not match real blocks, because braces somehow got mismatched. In newly written code, that shouldn't ever happen. Still, it's possible to end with spaghettified code blocks, when one refactors existing code base, and is not careful enough with search-replace, regex. Fortunately, there is always undo.

Still, I like this indent-style, I'll keep it for now.

Edit: well, that didn't last for a long. The indent-style, that is.

You see, the problem is, braces still determine to which code block a particular line belongs, but they aren't "visible", you have to parse code to find them. Syntax highlighting and good editor can help, but usually only one code block at the time. Maybe VS Code can put visual hints around every code block, but I didn't bother, visual cacophony most likely would be overwhelming. 

So, I already switched back to 1TBS, a few days ago. When in doubt, wisdom of the commons (most popular choice) might be, if not the best choice, the least atrocious.

Refactor, don't recycle

I come across situation where I'd need to render fields outside of current chessboard. That would entail having border on side(s) of rendered board, and then filling border with, say, white and light grey fields, and obviously taking care to match field size and alternating light vs. dark fields with what would be rendered for chessboard.

Obvious solution would be to recycle borders, as already provided by board descriptor. Obvious solution would also be wrong. You see, borders of rendered board were meant to be used for spacing-out image and for border text, like enumerating rows and columns, and not to be recycled to render off-board fields.

It's not that just purpose is violated. What would happen if, for instance, one would need both off-board fields and enumerated rows, columns? Another problem with that kind of "solution" is "mudding the water", i.e. internal structure, names don't match usage anymore, so it's much harder to figure it out what that piece of code actually does.

Real solution is to refactor code if change is small enough, or rework it completely if that's not the case. "Small enough" should be determined solely by the developer who'll do it. Rework might be better idea, it errs on cautious side with no ill effects beside taking a little bit longer if change actually was "small enough".

Also, rework doesn't mean everything has to be written from scratch. For basic stuff (like, in this instance, rendering field, piece) one should actually reuse (copy over and adapt) existing, working, proven snippets, functions, methods.

So, refactor if possible, rework if necessary, reuse what you can, but don't recycle code and it's design, intention, purpose, even if you could, with no known ill effects at the time. Development also includes constantly adapting to changing requirements. Sooner or later, there will be a requirement that challenges violated design, intention, purpose of recycled code.

This is also why it might take some time before I resume work on the book, I'll have to rework board renderer.

Unplanned changes

I'll have to change almost all of Python code used to render images for the book. Reason, it's legacy code.

One good definition of what legacy code is might be "code which currently works, but needs enhancements on it, and on the same time should not be broken, even if existing designs gets in the way of said enhancements".

This is, in my experience, one of the worst scenarios for any developer. While you sort-of could build scaffolding on a mine field, good luck trying to build skyscraper there. Once in a while it's good to retrospect on design decisions you've made, and decide is it still worth continuing with legacy code.

While I'm yet to stumble upon situation where it's difficult to render scene, I can certainly imagine scenes which would broke rendering code, and I can see that time coming. Also, by postponing re-implementation, I'd be left with much more scene code to convert.

As always with primarily design changes, what makes new design better then the previous one? Would better design justify all by itself all the effort, downtime, anguish endured? What is good design, after all?

Well, maybe in the next post.