DEV Community

Peter Wan
Peter Wan

Posted on

Refactor, revise, rebase

This week in my Open Source Development class, my classmates and I were given a task to think of three ways to refactor the code we wrote for our command-line tools.

Below, is my GitHub repository for my command-line tool gimme_readme:

GitHub logo peterdanwan / gimme_readme

gimme_readme is a command-line tool powered by AI that generates a comprehensive README.md file for your project. It analyzes multiple source code files at once, providing concise explanations of each file's purpose, functionality, and key components, all in a single, easy-to-read document.

gimme_readme

gimme_readme is a command-line tool powered by AI that generates a comprehensive README.md file for your project. It analyzes multiple source code files at once, providing concise explanations of each file's purpose, functionality, and key components, all in a single, easy-to-read document.

gimme_readme-0.1-demo-revised

See our 0.1 Release Demo!

Table of Contents

  1. Getting Started
  2. Usage
  3. Example Usage
  4. Supported Models by Providers
  5. Contributing
  6. Testing Locally
  7. Author

1. Getting Started

To get started with gimme_readme, follow these steps:

  1. Install the latest version of Node.js for your operating system.

  2. Run the following command to install gimme_readme globally:

    npm i -g gimme_readme
    Enter fullscreen mode Exit fullscreen mode

    NOTE: MAC/LINUX users may need to run sudo npm i -g gimme_readme

  3. Generate a configuration file by running in any folder you'd like:

    gr-ai -c
    Enter fullscreen mode Exit fullscreen mode

    This command creates a .gimme_readme_config file in your home directory. Do not move this file from this location.

  4. Open the .gimme_readme_config file and add your API…

Before we go into what I refactored, I think it's best I clarify what refactoring is. Refactoring is the process of improving the internal structure of your code without changing its current functionality. In comparison, revising has the connotation of making changes that could change the current behaviour of your program (hopefully for the better).

With definitions out of the way, let's get into what I refactored.

To me, the most obvious thing I could do to refactor my code was to facilitate better file organization within my repo.

I want to organize my repository in a way that tells the story of the project. By placing files in logically named folders, anyone should be able to navigate it easily and know where each file belongs.

This is what my repo's file structure looked like prior to my changes:

before

This is what my repo's file structure looks like now:

after

The hope with this refactoring of my project's file structure, is that contributors, as well as myself can quickly reason, "if I want to work on the ai-related logic of this program, I should probably look in my ai folder".

But wow - this was a lot of work to implement.

By attempting to move my ai_config folder and ai_models folder into the ai folder, I also had to update the plethora of other files that depended on the files within these folders.

Take a look at how many files I needed to change, simply because I wanted to rename my folders:

renaming-influences-a-lot

Look at this end result:

ai-folder

The end result, in my opinion, is a lot nicer - the right files are where they should be now.

As a side note, you'll notice that when I ran:

git status -s
Enter fullscreen mode Exit fullscreen mode

You could see files that had the R beside them - this is to indicate these files were renamed and that git is aware of this.

git is aware of this renaming because I had executed git mv commands such as this:

git mv tests/unit/ai_models/groqModels.test.js tests/unit/ai/models/groqModels.test.js
Enter fullscreen mode Exit fullscreen mode

Now - why should you care?

Well, if you were in a situation where you wanted to rename your existing files in a git repository, and you didn't use git mv to rename your files, you might encounter several issues:

  1. Loss of file history: If you manually rename a file (e.g., using your operating system's file explorer or a command like mv), git will interpret this as two separate actions: deleting the old file and creating a new one. This means you'll lose the file's history in git, making it harder to track changes over time.
  2. Confusion in diffs: When you review changes or create pull requests, the diff will show a file deletion and a new file creation instead of a simple rename. This can make code reviews more challenging and time-consuming.
  3. Merge conflicts: Renaming files without git mv can lead to more merge conflicts, especially if other team members are working on the same files.
  4. Incomplete tracking: git won't automatically track the relationship between the old and new filenames, which can complicate operations like reverting changes or cherry-picking commits.

By using git mv, you ensure that:

  1. File history is preserved: git maintains the file's entire history, just under a new name.
  2. Clean diffs: When you review changes, it's clear that a file was renamed rather than deleted and recreated.
  3. Smoother merges: git can handle merges more intelligently when it knows a file was renamed.
  4. Proper tracking: git correctly tracks the relationship between the old and new filenames, making operations like reverting changes easier.
  5. Efficiency: git mv is a single operation that both moves the file and updates Git's index, saving you from having to run separate mv and git add/rm commands.

In summary, you should use git mv to ensure that git can track the changes of your files, even when they're renamed.

Now - all that git mv / renaming business was my first iteration of changes that I wanted to implement.

The next change I made was actually quite small in comparison.

All I did with my next change was extract a function, based on the guidelines of this website here: https://refactoring.com/catalog/extractFunction.html.

This is what I had before I extracted my function:

before-extraction

This is what I had after I extracted my function:

extract-function

With regards to this particular refactoring of my code, I was actually reluctant to do it, since in my eyes, the original logic was actually clearer to me than the refactored version. Not that I find extracting functions inherently wrong, but more so because for this particular case, there was no reusability factor in it.

To give you a clearer idea of what I mean by reusability, let me show you an example of what I've seen in the Visual Studio Code repository.
In one of their files, src/vs/base/common/arrays.ts, they define the function, sortedDiff which defines a sub-function within it.

I think this would be a better example of when extracting a function as described in https://refactoring.com/catalog/extractFunction.html would be appropriate.

Check out the code below:

extracting-a-function-vscode

In this example, the sub-function (pushSplice) not only serves as a way of documenting certain steps - it also serves as logic that can be repeated within the outer function (sortedDiff).

This is when I would think it'd be more appropriate to extract a function - but others may think differently! For others, a function is worth more than comments explaining the code - I can also agree with that standpoint too.

But without going any further into that topic, let me show you the third change I made, where I took some common logic between 2 similar files, and placed it in another file. Below, is the extracted logic, placed into a new file:

extracted-logic

Below, you can see how my existing files benefited from this new file (there's a lot less code now in both files)!

example-1

example-2

With that, I have refactored my code in 3 different ways!

But wait - that's not the end of the lab just yet.

With each change I made to my repository for the purpose of refactoring, I have formalized those changes with git commits.

commit-history-before-rebase

However, there are times when you need to know how to squash multiple commits into one, so you don't bloat the history of your git repository.

Let's demonstrate how we can use the git rebase -i command (the i stands for interactive) to squash our commits.

Running git rebase main -i will open up a text editor, such that you can tell git how to handle your commit history - by default, this opens up vim.

interactive-rebase-before-squashing

Let's actually edit the file, such that we can tell git we want to squash our commits into one commit.

interactive-rebase-after-squashing

NOTE:

  1. to exit vim and save your changes:
    1. press ESC
    2. then :wq
    3. press ENTER
  2. to exit vim without saving changes:
    1. press ESC
    2. press :q!
    3. press ENTER

For me, I saved my changes and was greeted with this other interactive screen, which shows you which commits you're squashing:

confirmation

You'll also need to save and exit this screen too.

After doing so, you'll be greeted with something like this:

after

Let's run git log --all --graph, to see our changes visually.

git-log-all-graph

You'll notice the squashed commit's commit message doesn't look very pretty.

Let's make an amended commit, via git commit --amend. Below, you can see I've adjusted the commit message a bit.

edit-commit-message

Let's save, and get out of the vim.

After making adjustments to our commit history, we end up with something like this:

rebased-commit-message

Now, let's switch to our local main branch and merge the changes from our refactor branch by running the following:

git checkout main
# We should be able to do a simple fast-forward merge
git merge --ff-only refactor
Enter fullscreen mode Exit fullscreen mode

fast-forward-merge

Now, let me push my changes to my main branch on GitHub (normally I wouldn't do this, but this is for demonstration!).

github

With that, my commit was pushed to GitHub!

So to summarize, this week was a lot of fun, where we worked on making am minimum of 3 changes (where each change involved refactoring our code), and using git rebase --i main to squash our commits.

I hope the pictures and narrative helped!

Cheers!

Top comments (0)