Good Refactoring vs Bad Refactoring (www.builder.io)
from lysdexic@programming.dev to programming@programming.dev on 07 Sep 2024 19:20
https://programming.dev/post/19111269

#programming

threaded - newest

BehindTheBarrier@programming.dev on 07 Sep 2024 20:01 next collapse

I largely agree with this nodding along to many of the pitfalls presented. Except numbers 2s good refactor. I hope I won’t sound too harsh/picky for an example that perhaps skipped renaming for clarity on the other parts, but I wanted to mention it.

While I don’t use javascript and may be missing some of the norms and context of the lanugage, creating lamda functions (i don’t know the js term) and then hardcoding them into a function is barely an improvement. It’s fine because they work well with map and filter, but it didn’t address the vague naming. Renaming is refactoring too!

isAdult is a simple function with a clear name, but formatUser and processUsers are surprisingly vague. formatUser gives only adult FormattedUsers, and that should probably be highlighted in the name of formatUser now that it is a resuable function. To me, it seems ripe for mistaken use given that it is the filter that at a glance handles removing non-adult users before the formatting, while formatUser doesn’t appear to exepct only adult users from it’s naming or even use! Ideally, formatUser should have checked the age on it’s own and set isAdult true/false accordingly, instead of assuming it will be used only on adult Users.

Likewise, the main function is called processUsers but could easily have been something more descriptive like GetAdultFormattedUsers or something similar depending on naming standards in js and the context it is used in. It may make more sense in the actual context, but in the example a FormattedUser doesn’t have to be an adult, so a function processing users should clarify that it only actually creates adult formatted users since there is a case where a FormattedUser is not an adult.

eager_eagle@lemmy.world on 07 Sep 2024 20:30 collapse

Totally agree. The hardcoded isAdult: true repeated in all #2 examples seems like a bug waiting to happen; that should be a property dynamically computed from the age during access time, not a static thing.

nous@programming.dev on 08 Sep 2024 00:05 collapse

Or just a function. IMO computer properties are an anti pattern. Just adds complexity and confusion around what is going on - all to what? Save on a () when you access the value?

astrsk@fedia.io on 08 Sep 2024 01:21 next collapse

Properties are great when you can cache the computation which may be updated a little slower than every time it’s accessed. Getter that checks if an update is needed and maybe even updates the cached value then returns it. Very handy for lazy loading.

nous@programming.dev on 08 Sep 2024 09:05 collapse

Functions can do all this. Computed properties are just syntactic sugar for methods. That is it. IMO it makes it more confusing for the caller. Accessing a property should be cheap - but with computed properties you don’t know it will be. Especially with caching as your example. The first access can be surprisingly slow with the next being much faster. I prefer things to not do surprising things when using them.

astrsk@fedia.io on 08 Sep 2024 09:20 collapse

I get that, it’s a valid point. But in OOP, objects can be things and do things. That’s kinda the whole point. We’re approaching detailed criticism of contextless development concepts though so it kinda doesn’t matter.

eager_eagle@lemmy.world on 08 Sep 2024 04:59 collapse

Properties make semantic sense. Functions do something, while properties are something. IMO if you want to name something lazily evaluated using a noun, it should be a property.

nous@programming.dev on 08 Sep 2024 09:09 collapse

Functions do something, while properties are something.

This is my argument against them. Computed properties do something, they compute a value. This may or may not be cheap and adds surprising behavior to the property. IMO properties should just be cheap accessors to values. If it needs to be computed then seeing a function call can hint the caller may want to cache the value in a variable if they need to use it multiple times. With properties you need to look it up to know it is actually doing work instead of just giving you a value. That is surprising behavior which IMO I dislike in programs.

eager_eagle@lemmy.world on 08 Sep 2024 12:38 collapse

that we agree on: properties should be cheap to compute.

Making a simple ternary condition as a function instead of property is a wasted opportunity to make its usage cleaner.

nous@programming.dev on 08 Sep 2024 19:57 collapse

Make its usage cleaner? I don’t see how a property does that at all. We are talking about x.foo vs x.foo() really. And IMO the latter tells you this is a function that needs to do some work even if that work is very cheap. x.foo implies that you might be able to set the value as well. But with computed properties maybe not. Which IMO makes the program a bit harder to read and understand as you cannot simply assume it is a simple assignment or field access. It could be a full function call that does different things depending on other values or even if you are setting vs getting the value. I prefer things being more explicit.

eager_eagle@lemmy.world on 08 Sep 2024 20:10 collapse

Because you’re assuming foo won’t be renamed when it becomes a function. A function should start with a verb, say get_foo(), because just foo() tells me nothing about what the function does (or what to expect as output). If you make it a property, get_ is implicit.

So if the age is computed from the year of birth for example, it’s really e.g. thing.age or thing.get_age() - both of which are fine, but I’d pick the property version.

nous@programming.dev on 08 Sep 2024 22:57 collapse

Or just thing.age() which is fine and is fairly obvious it will return the age of the thing. And that is what is done in most languages that dont have computed properties. get_ on a method really adds no value nor clarity to things. The only reason foo() is ambiguous is because it is a bad name - really just a place holder. Missing out the brackets here adds no value either, just makes it hard to tell that you are calling a function instead of just accessing a property.

nous@programming.dev on 08 Sep 2024 00:02 next collapse

  1. Changing the coding style substantially

Well refactoring is the only place you can change the coding style… But really a change to the coding style should not be done blindly, but with by in from team before you start changing it. It is not uncommon for a team to make bad decisions throughout the course of a project, including the style they picked. And if you ask around you might find others have the same idea as you and don’t like the current style. Then you can have a conversation about it. But you should not jump in and start changing the style of a code base before you talk to your team.

One of the biggest problems I’ve seen is refactoring code while learning it, in order to learn it. This is a terrible idea. I’ve seen comments that you should work with a given piece of code for 6-9 months. Otherwise, you are likely to create bugs, hurt performance, etc.

I disagree with this. No refactoring for 6 to 9 months? That is just insane. IMO refactoring is a great place to learn how the code works. But it does need review of someone more experience to see if you are missing anything important. They can then point out why those bits are important and you just learned a bit more about the code base.

If no one else knows then what is 6 to 9 months going to achieve? The lesson here is to not refactor blindly, but to seek out others that understand the code before you refactor, and failing that spend more time trying to understand it before refactoring.

  1. Overly consolidating code

Another way to put this is don’t overly DRY your code. And here I would stick with the original code, not either refactor. It saves no lines and just makes the lines far longer. So much so it gives my a horizontal scrollbar on it…


But overall there are some good ideas in the post. Though I do dislike how they are using the term refactoring. All of these apply to any code you write, not just refactoring existing code.

Snarwin@fedia.io on 08 Sep 2024 01:40 next collapse

I appreciate that this article highlights the value of using of named functions in functional-style code. Too often, programmers assume that "functional programming" means using lambdas everywhere, when in my experience, lambdas are actually a (very mild) code smell.

nous@programming.dev on 08 Sep 2024 10:32 collapse

lambdas are actually a (very mild) code smell

Lambdas alone are not a code smell. That is like saying Objects or Functions or even naming things is a code smell just because you can use them in bad ways. It is just to broad a statement to be useful. At best you might say that large/complex anonymous lambdas are a code smell - just like large/complex and badly named functions are. You need to be specific about code smells, otherwise you are basically saying code is a code smell.

Snarwin@fedia.io on 08 Sep 2024 12:42 collapse

Well, remember, a code smell isn't something that's inherently bad, it's "a hint that something might be wrong".

I'm not saying that anyone should flag lambdas as a problem in code review, just that when you see one, it's probably worth taking a second to ask yourself if a named function would make more sense.

nous@programming.dev on 08 Sep 2024 19:52 collapse

It is such a weak smell though you might as well look at any bit of code you have and ask yourself if it is bad code. Lambdas are fine in a lot of places and the existence of them is not an indication of good or bad code. It is just a tool that can be used in lots of situations.

A better smell here is excessive inlineing causing a loss of context. Does not matter if it is a lambda, or a parameter to a function call, or a field in a object creation. None of those are signs of bad code, but if you cannot understand what something anonymous is doing you might want to give it a name. This does not mean creating a named function, but might just be assigning the lambda or parameter to a variable so it is named.

But on the flip side I find it you are struggling to name something then that can also be a smell that maybe it should just be inlined. Giving everything a name can create just as bad code as trying to inline everything. There is a balance in the middle somewhere. And the presence of a lambda does not really hint as to which way you want to go. So its existence is a very poor marker for code quality.

it’s probably worth taking a second to ask yourself

You can extend this to all code, not just lambdas. Any code you can take a second to ask yourself if you could write it better or more readable. If that is the bar then all code has a very weak code smell and singling out lambdas here seems arbitrary.

mindlesscrollyparrot@discuss.tchncs.de on 08 Sep 2024 10:03 next collapse

The author mentions that some of the changes broke things, but it’s a long way into the article before the word “test” appears. It’s only point 6/7 of his recommendations.

Making changes with no test coverage is not refactoring. It’s just rewriting. Start there.

LegoBrickOnFire@lemmy.world on 08 Sep 2024 10:05 collapse

SEO breaks the web, writer was a hero :)