DEV Community

Ioannis Potouridis
Ioannis Potouridis

Posted on • Updated on

Probably the hottest code refactoring you ever saw 🔥

I have some beliefs on programming.

I always re-write, re-think, research, re-invent and refactor.

That did cost me time in the beginning but now, it's not a problem.

Whilst doing that learned how and why things work in JavaScript.

I never settle, even if something works.

Everything matters, function names, variable names, even prop sorting.

I was doing a code review and reached to a function.

I need to mention here that refactoring took me no more than five minutes.

The function was responsible for formatting a given address depending on its country code.

E.g.

const address = {
  city: "Belgreen",
  countryCode: 'us',
  county: null,
  state: "Vermont",
  suburb: null,
  zip: "4636"
}
Enter fullscreen mode Exit fullscreen mode

The implementation was the following.

const addressTextView = () => {
  const city = address.city;
  const state = address.state;
  const zip = address.zip;
  const suburb = address.suburb;
  const county = address.county;

  switch (address.countryCode) {
    case 'uk':
      return (
        (zip != null ? zip + ', ' : '') +
        (suburb != null ? suburb + ', ' : '') +
        (city != null ? city + ', ' : '') +
        (state != null ? state : '')
      );
    case 'us':
      return (
        (city != null ? city + ', ' : '') +
        (state != null ? state + ', ' : '') +
        (zip != null ? zip : '')
      );
    case 'nz':
      return (
        (zip != null ? zip + ', ' : '') +
        (city != null ? city + ', ' : '') +
        (county != null ? county + ', ' : '') +
        (state != null ? state : '')
      );
    default:
      return (
        (zip != null ? zip + ', ' : '') +
        (suburb != null ? suburb + ', ' : '') +
        (state != null ? state + ', ' : '') +
        (county != null ? county : '')
      );
  }
}
Enter fullscreen mode Exit fullscreen mode

The first thing that bothered me was the ternaries in each case.

Then the repetition.

I started thinking of every return as an array of keys in different order.

I didn't care about null values.

I just started creating patterns.

//
  switch (address.countryCode) {
    case 'uk':
      return [zip, suburb, city, state].join(', ');
    case 'us':
      return [city, state, zip].join(', ');
    case 'nz':
      return [zip, city, county, state].join(', ');
    default:
      return [zip, suburb, state, county].join(', ');
//
Enter fullscreen mode Exit fullscreen mode

Then, I saw the pattern and I created a function to handle each return.

const joinStrings = (...args) => args.join(', ');
Enter fullscreen mode Exit fullscreen mode

And the switch looked like this.

//
  switch (address.countryCode) {
    case 'uk':
      return joinStrings(zip, suburb, city, state);
    case 'us':
      return joinStrings(city, state, zip);
    case 'nz':
      return joinStrings(zip, city, county, state);
    default:
      return joinStrings(zip, suburb, state, county);
//
Enter fullscreen mode Exit fullscreen mode

Then I did something that amazed some people.

The way I filtered out null values from each array.

const joinStrings = (...args) => args.filter(Boolean).join(', ');
Enter fullscreen mode Exit fullscreen mode

And final code was this.

const joinStrings = (...args) => args.filter(Boolean).join(', ')

const formatAddress = ({ city, county, countryCode, state, suburb, zip }) => { 
  switch (countryCode) {
    case 'uk':
      return joinStrings(zip, suburb, city, state);
    case 'us':
      return joinStrings(city, state, zip);
    case 'nz':
      return joinStrings(zip, city, county, state);
    default:
      return joinStrings(zip, suburb, state, county);
  }
};
Enter fullscreen mode Exit fullscreen mode

My thoughts.

Both functions work. Business is happy.

It's OK to deliver it, but...

always try to improve and never settle if something works.

We had a function called addressTextView which was not very clear what it does. It also used the address object from the parent scope.

Then we had a lot of logic with ternaries which where not very clear to read at first either.

I renamed the function to formatAddress to be clear and passed the address object as an argument.

I isolated the logic to a different function named joinStrings. That function is independent from formatAddress and can be reused if necessary.

We also went from 45 lines of code down to 13. 😍

That's it.

I am not bragging, I'm trying to say it doesn't matter if it works but if you want to learn and grow as a developer there are a lot of ways to do that.

Top comments (30)

Collapse
 
bsaad profile image
bsaad

Great post, I like how you took the time to explain step by step.

I'd suggest also the following change to make the "business rules" clearer by extracting them from the formatting logic:

const formatMap = {
  uk: ["zip", "suburb", "city", "state"],
  us: ["city", "state", "zip"],
  nz: ["zip", "city", "county", "state"],
  default: ["zip", "suburb", "state", "county"],
}

const formatAddress = (address) => { 
  const format = formatMap[address.countryCode] || formatMap.default;
  return format.map(field => address[field]).filter(Boolean).join(', ');
};
Collapse
 
kelerchian profile image
Alan • Edited

Unless formatMap is something that needs to be dynamic, I suggest not to do this.

This mixes up type and data and also smells like premature abstraction.

Collapse
 
kamalhm profile image
Kamal

this is also what i like to do whenever a switch case appears

Collapse
 
tomazfernandes profile image
Tomaz Lemos

Actually I’ve just written a post about this kind of pattern, if you’d like to take a look:
dev.to/tomazlemos/keeping-your-cod...

Collapse
 
potouridisio profile image
Ioannis Potouridis

Thanks for sharing this. 🙏

Collapse
 
potouridisio profile image
Ioannis Potouridis

Well one way or another the formatAddress function needs to know the business rules. If I went with your approach I would pass the rules as an argument.

Collapse
 
ancientswordrage profile image
AncientSwordRage

This is the final image in a magneto_perfection.jog meme. I love it.

Collapse
 
faezshaeran profile image
Faiz Shahiran

this is nice.. considering formatMap value can be saved in config file or database. Its easier to scale.

Collapse
 
tomazfernandes profile image
Tomaz Lemos

I’d never seen this pattern in JS, looks just as nice as in Java 👍🏼👍🏼

Collapse
 
aleksikauppila profile image
Aleksi Kauppila

Thanks for posting Ioannis, good job!

You can take this to the next level with Replace conditional with Polymorphism. Even though you have successfully improved the code in this example, every new format will require you to modify this algorithm.

Collapse
 
potouridisio profile image
Ioannis Potouridis

Great! Thanks for sharing.

Collapse
 
tool3 profile image
Tal Hayut

const { city, state, zip, suburb, county } = address;

Literally had to 😂

Collapse
 
xdega profile image
Liam Hockley

I gotta say. The refactor looks good, but the lack of consistency in the order of your arguments really triggers me.

While you are mapping the args, the order doesn't matter in terms of working code, but it could be a wee bit more readable to the human eyes that work with the code. ;)

Collapse
 
potouridisio profile image
Ioannis Potouridis • Edited

Hmm, I don't know if you're talking about joinStrings function which in this case the order of the arguments does matter. On the other hand, I could simply pass a single array argument which was my first attempt.

Collapse
 
the3rdc profile image
Cody Crumrine • Edited

I like the refactor in general, but I'm not 100% sure I'm on board with the joinStrings function as opposed to a .join().filter() in each switch case. Seems like maybe more a case of abstraction for abstraction's sake than valuable code reuse.

I'd also maybe prefer a more explicit null check (still using the filter method) for clarity sake. But that may be just be preference.

All that aside, the simplification from all the conditional checks and concatenations to a single join and filter of an array is spot on. 👍

Collapse
 
muramasah profile image
Felipe

Hi Ioannis, I think you cleaned very well the code, but it is still rigid an fragile. For example, try to change the way the message is formated in some countries, like using dots for some properties instead of comas, or try to add a new field to every country. In every case you must change the written code breaking with the SOLID's open-closed principle.

The source of evil here is the switch, your function formatAddress has two responsabilities, (1) to determine what country code has the adress object, and (2) choose the properties to be passed to joinStrings for each case.

An easy pattern that bring us some flexibility in these cases is the chain of responsabilities. If you dislike this approach you have others more functional but the responsabilities coupled there are a big issue from my point of view, because limits the extention of features that has a lot of probabilities to change.

Btw, all of I'm saying here do not take away the fact that you have done an awesome job until here!

Sorry for the bad english,
Have fun!

Collapse
 
potouridisio profile image
Ioannis Potouridis

You're totally correct.

The format and number of countries we serve is fixed so I didn't take into consideration these principles. I should but this was a function with a specific purpose and posted this as an example for cleaner code. Thank you for your feedback and don't worry for your English. 😀

Collapse
 
brewnode profile image
Internet of Brewing

Code is art! Keep refactoring, striving for beauty. Simplify as much as possible but no more.

Collapse
 
zpwebbear profile image
Dmitriy Braginets

Beautiful solution ;)

Collapse
 
jwp profile image
John Peters

Excellent post, thanks.

Collapse
 
angryziber profile image
Anton Keks

Please remember that zip is called "postal code" everywhere outside of US

Collapse
 
potouridisio profile image
Ioannis Potouridis

Tell that to my back end! 😍

Some comments may only be visible to logged-in visitors. Sign in to view all comments.