How to avoid duplicate if checks when overriding a method in JavaScript?

I have the following parent class method in JavaScript: class Parent { jump() { if (!this.isDead()) { this.lastActivityTime = Date.now(); } } isDead() { // returns a boolean } } In my child class, I'm overriding the jump() method like this: class Child extends Parent { jump() { if (!super.isDead()) { super.jump(); this.speedY = 30; } } } I have a duplicate if check for isDead(): once in the parent class and once in the child class. How can I refactor the code to avoid repeating this check, while still maintaining functionality and ensuring the parent method is called correctly? What is the best practice for handling such situations in JavaScript to make the code cleaner and more maintainable?
19 Replies
Chooβ™šπ•‚π•šπ•Ÿπ•˜
It seems like you are trying to overoptimize. Anything you can do to avoid the duplicate check will make the code more complex and will not improve efficiency at all. If you insist on still doing this, I can think of two ways to do it: 1) Return a boolean from jump() to indicate if the jump happened. The Child can then call super.jump() unconditionally and set speedY only if that call returns true. 2) Make jump() accept a callback that it calls along with setting the value of this.lastActivityTime. I advise against using either approach, and I doubt any other creative solutions will actually improve efficiency beyond what you already have. I just realized a possible third option. You could call super.jump() unconditionally AND set speedY unconditionally if the new value of speedY has no effect on a dead entity. This still does not seem better than what you have.
Jochem
Jochemβ€’2w ago
agreed, this sounds like overoptimization as long as that isDead() call is simple enough. If it's just a return this.dead or return this.hp <= 0 or whatever, just call it as much as you want
dys πŸ™
dys πŸ™β€’2w ago
Once dead is true, it's only getting called once in any case. super.jump() never gets called in the child when the call returns negative in the parent. The only alternative I could see would be to throw an error since you want execution to essentially halt if dead. That, or use guard clauses like:
class Parent {
jump() {
if(this.dead) return
this.lastActivityTime = Date.now()
}

get dead() {…}
}

class Child extends Parent {
jump() {
if(this.dead) return
super.jump()
this.speedY = 30
}
}
class Parent {
jump() {
if(this.dead) return
this.lastActivityTime = Date.now()
}

get dead() {…}
}

class Child extends Parent {
jump() {
if(this.dead) return
super.jump()
this.speedY = 30
}
}
Those are just to reduce the amount of nesting though, not to reduce the number of calls.
ἔρως
ἔρως‒2w ago
the question is: how do you check that it is dead? how much work do you have to do?
Abc
AbcOPβ€’2w ago
I think this is the best solution. But the if-check is not needed in the child class.
Joao
Joaoβ€’2w ago
Please correct me if I'm reading this incorrectly, but it seems to me that you're solving the wrong problem. Based on the code snippet provided, at least, the only reason to invoke the parent's jump method is to set the last activity time. First of all, I would consider the easiest and most straight forward solution to just "duplicate" the jump method on the children, with the added portion to update the speed. I would argue that this tiny bit of duplication is worth it... in fact, I would argue this isn't duplicated code at all, even though it might seem like it. What "duplicated code" really refers to are sections of code that achieve the same goal within the same context. Two functions, or whatever, that are doing the same thing for different reasons are not duplicated code, since one could be updated at any time without affecting the other. In this case, the parent jump method logs the activity but the child's jump method also updates the speedY. If you really need to do the same thing, why bother overwriting the method? That's the entire point of inheritance, take advantage of it. As an example of how you might want to change the child's jump method, you might want to add additional checks such as isCrawling, isSwimming, isFlying... since not all instances of child might have any or all of those properties, it's clear that the jump method should be independent from the parent. Therefore, not duplicated code. That all said, if you really want to have the activity logged whenever you update the speed (and probably other properties) consider using getters/setters:
class Parent {
#lastActiveTime = 0;
#speedY = 0;
isDead = false;

get speedY() {
return this.#speedY;
}

set speedY(speed) {
this.#lastActiveTime = Date.now();
this.#speedY = speed;
}

get lastActiveTime() {
return this.#lastActiveTime;
}
}

class Child extends Parent {
jump() {
if (this.isDead) return;
this.speedY = 30;
}
}
class Parent {
#lastActiveTime = 0;
#speedY = 0;
isDead = false;

get speedY() {
return this.#speedY;
}

set speedY(speed) {
this.#lastActiveTime = Date.now();
this.#speedY = speed;
}

get lastActiveTime() {
return this.#lastActiveTime;
}
}

class Child extends Parent {
jump() {
if (this.isDead) return;
this.speedY = 30;
}
}
Notice how doing this you don't even need a jump method on the parent anymore. It's nice and concise, although this may lead to challenged in either testing or debugging since now you have a single action doing multiple things: use sparingly and with caution. Or, simply "duplicate" code. As long as you understand when it really is duplicate and when it isn't. Also note, I'm using private properties here not only for correctness, but also because it allows using the same property name for the getter/setter. Otherwise you would have to do _speedY, etc.
ἔρως
ἔρως‒2w ago
dont use Date.now() for this, as it has intentional reduced precision for example, firefox is intentionally imprecise to 2ms, but can be set to 100ms use performance.now() as it is an high precision timer besides, performance.now() doesnt just return milliseconds, but goes lower (microseconds, i think - cant verify that) yes, it's supposed to be accurate to 5 microseconds
Joao
Joaoβ€’2w ago
I suspect that the millisecond variant in this case is irrelevant.
ἔρως
ἔρως‒2w ago
yes, but Date.now() can go back in time when the system clock is adjusted automatically with ntp (built into windows) performance.now() always moved forward
dys πŸ™
dys πŸ™β€’2w ago
I'd use new Date() for semantic reasons.
ἔρως
ἔρως‒2w ago
so, you use the "wrong" tool because of semantics?
Joao
Joaoβ€’2w ago
Why is it wrong? You don't know the context. If the requirement is to keep a log of when a component was last active, you don't want to use relative times. Use whatever works best and worry about performance when you ahve a reason to care about performance.
ἔρως
ἔρως‒2w ago
it's not about performance it's about Date.now() being unsuitable for this type of thing it doesn't have enough precision and it can skip back and forwards in time that type of unexpected behaviour can really mess up a game, which is what looks like the code is for
Joao
Joaoβ€’2w ago
Again, you don't know what the context is so you don't know whether it is suitable or not. A few milliseconds in variance might also be perfectly acceptable for this game.
it doesn't have enough precision and it can skip back and forwards in time
You can account for that by other means...
ἔρως
ἔρως‒2w ago
im not talking about a few milliseconds
Joao
Joaoβ€’2w ago
but anyway, this isnt' even what this thread is about!
ἔρως
ἔρως‒2w ago
this is about the jump function i know
Jochem
Jochemβ€’2w ago
if it's being used to track jumping and stuff, it can cause really weird behavior it's a valid thing to point out, given how odd the bugs caused by inconsistent timing in video game logic can be
Joao
Joaoβ€’2w ago
Sure but we don't know that based on the question asked. There are other things that could be pointed out that will get us off-topic for no reason...
Want results from more Discord servers?
Add your server