2019-10-07 A tip with diffing (and committing) program structure changes

As I mentioned last week, the fact that diff works on a line basis is sometimes a source of trouble. Consider this case. Assume that we have a simple JavaScript module greets (I guess that a very similar case could be made for Java classes, Python modules etc.).

module.exports = {
	hello: function hello(who) {
		return `Hello ${who}!`;
	},

	bye: function bye(who) {
		return `Bye, ${who}!`;
	},
}

(Yes, it’s very primitive, it could make use of fat arrow functions etc., but please bear with me.)

Let us now assume that instead of exporting an object with two functions, we now want to export a function accepting one argument (a language code) and exporting an object containing keys hello and bye, much like before. So, we introduce the following changes.

module.exports = function(locale) {
	if (locale === 'en') {
		return {
			hello: function hello(who) {
				return `Hello ${who}!`;
			},

			bye: function bye(who) {
				return `Bye, ${who}!`;
			},
		}
	}
}

Now, diffing the two results in a mess (note that I use a real diff indtead of git-diff here, but this is irrelevant).

$ diff -u greets-1.js greets-2.js
--- greets-1.js 2019-09-15 22:54:33.421816250 +0200
+++ greets-2.js 2019-09-15 22:53:56.432355439 +0200
@@ -1,9 +1,13 @@
-module.exports = {
-       hello: function hello(who) {
-               return `Hello ${who}!`;
-       },
+module.exports = function(locale) {
+       if (locale === 'en') {
+               return {
+                       hello: function hello(who) {
+                               return `Hello ${who}!`;
+                       },

-       bye: function bye(who) {
-               return `Bye, ${who}!`;
-       },
+                       bye: function bye(who) {
+                               return `Bye, ${who}!`;
+                       },
+               }
+       }
 }

As you might imagine, for longer, real-life code the situation can get much worse. And if the whole thing is kept in Git, chances are that you are going to look at diffs a lot of the time, so they’d better be more readable!

There is, however, a simple trick which allows for (slightly) more readable diffs. Instead of committing this change in one go, let us first introduce a purely technical commit like this:

module.exports = {
			hello: function hello(who) {
				return `Hello ${who}!`;
			},

			bye: function bye(who) {
				return `Bye, ${who}!`;
			},
}

See what I did here? I have just indented everything inside the exported object by two tabs. (Deciding what and how much to indent is, of course, a human’s call every time.)

Now, the diff between this and the previous version isn’t that bad:

diff -u greets-1.js greets-1b.js
--- greets-1.js	2019-09-15 22:54:33.421816250 +0200
+++ greets-1b.js	2019-09-21 08:22:17.826641850 +0200
@@ -1,9 +1,9 @@
 module.exports = {
-	hello: function hello(who) {
-		return `Hello ${who}!`;
-	},
+			hello: function hello(who) {
+				return `Hello ${who}!`;
+			},

-	bye: function bye(who) {
-		return `Bye, ${who}!`;
-	},
+			bye: function bye(who) {
+				return `Bye, ${who}!`;
+			},
 }

What is way more important, however, is the diff between this intermediate stage and the final one:

diff -u greets-1b.js greets-2.js
--- greets-1b.js	2019-09-21 08:22:17.826641850 +0200
+++ greets-2.js	2019-09-21 08:25:54.175578419 +0200
@@ -1,4 +1,6 @@
-module.exports = {
+module.exports = function(locale) {
+	if (locale === 'en') {
+		return {
			hello: function hello(who) {
				return `Hello ${who}!`;
			},
@@ -6,4 +8,6 @@
			bye: function bye(who) {
				return `Bye, ${who}!`;
			},
+		}
+	}
 }

See? This is now much more readable than previously!

Now, there are perhaps other ways to solve this problem. Both the regular, GNU diff and git-diff have at least one option that might help here: --ignore-space-change, or -b for short. Here is the result of using it:

diff -bu greets-1.js greets-2.js
--- greets-1.js	2019-09-15 22:54:33.421816250 +0200
+++ greets-2.js	2019-09-21 08:25:54.175578419 +0200
@@ -1,4 +1,6 @@
-module.exports = {
+module.exports = function(locale) {
+	if (locale === 'en') {
+		return {
	hello: function hello(who) {
		return `Hello ${who}!`;
	},
@@ -6,4 +8,6 @@
	bye: function bye(who) {
		return `Bye, ${who}!`;
	},
+		}
+	}
 }

This is way better than whet we started with, but I’d argue that it is still not as good as my semi-manual solution, since it uses the indentation from greets-1.js to show the “matching” lines (i.e., ones only differing by whitespace).

Yet another way to solve the problem of illegible diffs is to use something else in place of GNU diff. There are many such tools, and Git is capable of using them to show diffs. Run git difftool --tool-help to see the list, and git difftool -t <tool-name> <commit1> <commit2> to use a particular tool instead of a regular diff. I tried a few of them, and the results were varied. Some of them gave very nice diffs, some of them were closer to gibberish. Some of them gave nice diffs but colored in a way that didn’t help at all. In any case, I am not a great fan of GUI tools, but I admit that using e.g. kdiff3 instead of the regular GNU diff did help in this particular case, and basically rendered my trick with making two commits useless. On the other hand, this won’t help if you review pull request on some web-based app like BitBucket.

CategoryEnglish, CategoryBlog, CategoryJavaScript, CategoryGit