How I Refactored my Code
Tags: opensource
Published: 2021-10-16 01:41:05

This week, I noticed that some functions in [my static site generator](https://github.com/oliver-pham/silkie) (SSG) were hardcoded with complex logic and "magic values", so I decided to focus on refactoring them. Without cleaning them up, maintaining them would be a tragedy. For instance, there was a function spanning 36 lines of code with 8 `if/elif` statements. Some of the statements even have nested `if/elif` statements themselves. You can find the function referenced in [this issue](https://github.com/oliver-pham/silkie/issues/20).

## Code Refactoring

To resolve the above issue, I thought the best approach was to avoid reinventing the wheel and save myself hours of debugging: use a third-party library. After implementing a [Python implementation of John Gruber’s Markdown](https://github.com/Python-Markdown/markdown), 36 lines of code were cut down to a single function call. I've not benchmarked my SSG after the change, but in terms of code readability, it's certainly worth the overhead caused by the library.

Another [part that needed to be refactored](https://github.com/oliver-pham/silkie/issues/19) was the function for extracting a file name without its (multiple) extensions from a file path. For some reason, there was confusing and irrelevant validation of the file extension inside the function.
```python
# Inside the function
if pathlib.Path(file_path.split('.')[0]).stem == ".md" or pathlib.Path(file_path.split('.')[0]).stem == ".txt":
  return pathlib.Path(file_path.split('.')[0]).stem 
elif pathlib.Path(file_path.split('.')[1]).stem == ".md" or pathlib.Path(file_path.split('.')[1]).stem == ".md": 
  return pathlib.Path(file_path.split('.')[0]).stem 
elif pathlib.Path(file_path.split('.')[1]).stem == ".json" or pathlib.Path(file_path.split('.')[1]).stem == ".ini": 
  return pathlib.Path(file_path.split('.')[0]).stem 
else: 
  return pathlib.Path(file_path.split('.')[0]).stem
```
Since that's already checked at the beginning of the program, I simply [removed the redundant checks](https://github.com/oliver-pham/silkie/blob/22003aa527adc7681c712dc3a84c35896d30a0ec/silkie.py#L106).

The [last piece of refactoring](https://github.com/oliver-pham/silkie/issues/18) was probably the most problematic one. There was a block of code for setting the value of the options read from a JSON configuration file for generating an HTML document.

```python
 def silkie(input_path, stylesheet, lang, config): 
     """Static site generator with the smoothness of silk""" 
     try: 
         # Set options for generating HTML document from config file
         if config is not None: 
             with open(config, "r", encoding="utf-8") as f: 
                 config_item = json.load(f) 
                 if "input" in config_item: 
                     input_path = config_item["input"] 
                 else: 
                     input_path = None 
                 if "stylesheet" in config_item: 
                     stylesheet = config_item["stylesheet"] 
                 if "lang" in config_item: 
                     lang = config_item["lang"] 
  
         kwargs = dict(input_path=input_path, 
                       stylesheet_url=stylesheet, lang=lang) 
         options = GeneratorOptions( 
             **{k: v for k, v in kwargs.items() if v is not None}) 
         # Clean build 
         shutil.rmtree(DIST_DIRECTORY_PATH, ignore_errors=True) 
         makedirs(DIST_DIRECTORY_PATH, exist_ok=True) 
         # Generate static file(s) 
         if path.isfile(input_path) and is_filetype_supported(input_path): 
             generate_static_file(options) 
         if path.isdir(input_path): 
             for extension in SUPORTED_FILE_EXTENSIONS: 
                 for filepath in glob.glob(path.join(input_path, "*" + extension)): 
                     options.input_path = filepath 
                     generate_static_file(options) 
     except OSError as e: 
         click.echo( 
             f"{TextColor.FAIL}\u2715 Error: Build directory can't be created!{TextColor.ENDC}") 
     except FileNotFoundError: 
         print(f'Error: There is no Json File "{config}" !') 
     except json.JSONDecodeError: 
         print(f'Error:Invalid JSON syntax!" ') 
```

I found this too irrelevant to be included in the main method, so I [extracted to a method](https://github.com/oliver-pham/silkie/blob/22003aa527adc7681c712dc3a84c35896d30a0ec/silkie.py#L35-L48) inside the `GeneratorOptions` class.

During the process of refactoring, I noticed a logic flaw with the above code. If the `input_path` was not included in the configuration file, it could crash the program (due to `input_path` being set to `None`). Thankfully, it was [fixed](https://github.com/oliver-pham/silkie/blob/22003aa527adc7681c712dc3a84c35896d30a0ec/silkie.py#L38-L44) before my code was committed. With that done, it was time for rebasing and squashing!

## Git Interactive Rebase
It is indeed unnecessary to keep all the commits of each iteration of refactoring, so squashing them sounds like a good idea. After entering `git rebase main -i` into my command line, I edited my commit message one last time to make sure it's detailed and clear enough. After squashing all my commits into a [single commit](https://github.com/oliver-pham/silkie/commit/22003aa527adc7681c712dc3a84c35896d30a0ec), I could finally merge my `refactoring` branch, where I'd been refactoring my code, into the `main` branch.

## Conclusion
Luckily, throughout the code refactoring process, I didn't encounter any other major issues or crashes. It was not only a good chance to clean up my project but also a wonderful opportunity for me to learn more about Git Interactive Rebase and amending a commit.