Shailesh Vasandani
Posted on January 2, 2021
Blogs that show an estimated reading time can get upwards of 400% increased click-through rates. Since I roll my own stack for my statically built blog posts, I thought it would be a fun exercise to try and add this useful metric to my blog page.
The stack
In general, HTML templaters are designed to take custom data and merge them with predefined templates. My current blog post templating setup is a single Java program which uses almost Markdown-like syntax to insert content into a prewritten HTML file. I won't go into the details too much, but suffice it to say that it regenerates both the blog posts and any internal links — for example, the "Recommended Posts" section at the bottom. Let me know in the comments if you'd like to see a post on how I made my own templating system!
Aside from each blog post, it also generates a JSON file that my blog page taps into to show the post previews. By doing this, I can effectively have a purely static site that is as responsive as something like a WordPress site. Overall, the stack consists of Java as the site generator, and HTML, CSS, and JavaScript as the frontend technologies.
Getting the actual numbers
Calculating reading time is supposedly quite simple — just take the number of words in your article, and divide by the average reading speed of approximately 265 words. When it comes to blogs with code, however, this number can change dramatically. If the code is very dense, even a few words can take a minute or two. Excessively verbose code, however, can just be skimmed over. I decided that I would simply count the code by line, assigning the equivalent of about 1 line per word.
That may seem very little, but remember that code often has lots of lines containing no more than a single character, and also since I try to write my code to be readable and atomic; that is, each line should be short, sweet, and understandable. In addition, I decided to reduce the target speed from 265 to 200, solely because the number seems a lot nicer. Why not 250, you ask? I don't know. Arbitrary numbers in hand, the formula to calculate reading time is pretty simple:
readingTime = (numWords + linesOfCode) / 200;
Code is never so simple, is it
While it would be super useful to simply plop that formula in the Java file somewhere and have everything work, that would be far too easy. You see, I wrote my Java templating program with the goal of extracting repeated code and making functions testable. To this end, I have lots of functions that each do one thing and then call other functions, passing along any necessary variables as parameters and eventually returning the template String
that becomes the final product.
Since Java can only return one value (excluding using arrays to return multiple), any other information that needs to be returned needs to be passed in as an argument and changed inside the inner function. However, int
s in Java are passed-by-value (well technically everything is, but we conveniently ignore that). This means that I can't simply pass in a pointer to a wordCount
variable and update it inside the inner function, like I might be able to in C. So we're stuck in a rut — how do we get out?
Enter the hackiest solution I've probably ever done so far. Instead of passing an int
into a function, I'm passing in an int
array of length 1. Java passes this reference along, and I'm able to change the value of the first element in this single element array, bubbling up this value to the outer function. Because I have about three or so layers of nesting, my functions look a little like this:
public String buildHTML(String filename) {
// ...
int[] wordCount = new int[1]; // pass by "reference"
String content = buildContent(fileScanner, wordCount);
readingTime = wordCount[0] / 200;
// ...
}
public String buildContent(Scanner fileScanner, int[] wordCount) {
// ...
else if (lineArray[0].toUpperCase().equals("P")) {
content += buildParagraph(lineArray[1], wordCount);
} else if (lineArray[0].toUpperCase().equals("CODE")) {
content += buildCode(fileScanner, wordCount);
}
// ...
}
public String buildParagraph(String pData, int[] wordCount) {
// ...
wordCount[0] += content.split(" ").length; // actually setting the data
//...
}
public String buildCode(Scanner fileScanner, int[] wordCount) {
// ...
while (fileScanner.hasNextLine()) {
wordCount[0]++; // one "word" per line
// ...
}
// ...
}
As you can see, we're successfully able to pass that value down the chain and read it when it comes back. Not bad for a pass-by-value language like Java. To see the end result, take a look at my blog page and look at the top of any of the posts.
I hope that hearing about how I added this feature helped inspire you to tackle some annoying bug or implement an interesting feature idea you have. Let me know down in the comments if you've ever had to pull a hacky fix like I have just to fix one super tiny thing!
As always, don't forget to follow me for more content like this. I'm currently writing on dev.to and Medium, and your support on either platform would be very much appreciated. I also have a membership set up, where you can get early previews of articles and exclusive access to a whole bunch of resources. Also, if you've particularly enjoyed this post, consider supporting me by buying me a coffee. Until next time!
Posted on January 2, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.