DEV Community

Tue
Tue

Posted on

Refactoring code

SOOO! For the last few weeks, with the help of other students in the course, my static site generator program has become very functional, but it has also grown in size and I was definitely in "technical debt". It was high time to refactor the code base.

Sit tight, because I have a lot to cover

Code refactoring

Code refactoring meme

Below are the steps I took to repay my "technical debt"

Changing file paths handling to 'path' module

Most of my operations related to any file path were done using regEx or string functions, for example:

else if(stat.isFile() && filePath.split('.').pop() == "txt") {
    createHtmlFiles(filePath, "txt");
//... 
const fullFilePath = `${outputPath}/${filePath.match(/([^\/]+$)/g)[0].split('.')[0]}.html`; 
Enter fullscreen mode Exit fullscreen mode

They don't look very readable, especially all the regular expressions. After working on others' code, I found that they used 'path' module which I also had used before, I just questioned myself why did I not do that. Below are some of the changes, they turned out to be easier to figure out what are they trying to achieve

else if (stat.isFile() && path.extname(filePath) == ".txt") {
    this.createHTMLFile(filePath, ".txt");
}
//...
const fullOutputPath = path.join(this.outputPath, `${path.basename(filePath, fileType)}.html`)
Enter fullscreen mode Exit fullscreen mode

Change variable names

Not all function and variable names made sense. My function createHTMLFiles() only create one file at the time => createHTMLFile(), my variable fullFilePath left me a question which is "hhhm, what fullFilePath, did I mean the full output path. of a file" => fullOutputPath.

More changes were made

  • fileToHtml => fileToHtmlCreator because this variable is a HtmlCreator object
  • paragraphObj => bodyObj because the name was outdated, it was a 'paragraph object' for .txt files but now it's more like a 'html body object'

Extracting a function

I noticed that this piece of code was used twice in a similar way to write html files to the output folder, line 80, line 168

fs.writeFile(fullFilePath, fileToHtml.renderHTML().replace(/<html>/, `<html lang="${option.lang ? option.lang : defaultLang}">`), (err) => {
      if(err) 
        return console.error(`Unable to create file ${fullFilePath}`);
      console.log(`${fullFilePath} is created`);
    });
Enter fullscreen mode Exit fullscreen mode

I then wrote a separate function that does the same job to reduce duplicate code

writeHTMLFile = (fullOutputPath, fileToHtmlCreator) => {
        fs.writeFile(
            fullOutputPath,
            fileToHtmlCreator
                .renderHTML()
                .replace(/<html>/, `<html lang="${this.lang}">`),
            (err) => {
                if (err)
                    return errorToConsole(`Unable to create file ${fullOutputPath}`);
                else console.log("\x1b[36m", `${fullOutputPath} is created`, "\x1b[0m");
            }
        );
    };
Enter fullscreen mode Exit fullscreen mode

Refactoring the way of catching --config option

I was also not satisfied with the way the program handled --config option, so I rewrote most of the code. My approach was to catch the --config option, parse the .json first before other 'options', override the option object and return with error if --input is not specified

if(option.config){
  try {
    let configData = fs.readFileSync(option.config);
    let configOptions = JSON.parse(configData); 
    for(const [key, value] of Object.entries(configOptions)) {
      value || value.length > 0 ? option[`${key}`] = `${value}` : option[`${key}`] = undefined;
    }
    if(!option.input) {
      console.error('\x1B[31m', `error: input <file or directory> is not specified in config file ${option.config}`, '\x1B[0m');
      process.exit(-1);
    }
  } catch(error) {
  console.error('\x1B[31m', `Can't read or parse config file ${option.config}\n ${error}`, '\x1B[0m');
  process.exit(-1);
  }
}
Enter fullscreen mode Exit fullscreen mode

With this change, the number of lines drastically reduced.

Extracting a class

After refactoring the index.js file, I wanted to get rid of global variables so I came to a decision to create a class/module in a new file ssg.js that does all the file processing while index.js takes care of the command line. Therefore, I moved all global variables to be data members of SSG class and all functions into SSG class, I also tweaked them a bit to fit class syntaxes.

It definitely looks clearer now which also makes future debugging easier.

if(option.input) {
  var ssg = new SSG(option.input, option.output, option.lang);
  ssg.processInput(option.input);
}
Enter fullscreen mode Exit fullscreen mode

Squashing all the commits into one

Each step refactoring the code base was one or more commit, since we usually care about the end result not really about the process, it's better to squash all the commits into one commit with a clear message consists of bullet points of the changes. The steps are:

git rebase main -i
Enter fullscreen mode Exit fullscreen mode

Choosing which commit to be squashed and moving on to changing all commit messages into one

git commit --amend
Enter fullscreen mode Exit fullscreen mode

In the end, I pushed the changes to GitHub, it was quite some work!

Checkout THE COMMIT

Top comments (0)