This tiny mistake completely ruins your code

Last updated on October 16, 2024
This tiny mistake completely ruins your code

I was coding the other day and stumbled upon something atrocious.

Do you see it?

Let's zoom in a bit more:

This line:

Please don't do this in any language.

Don't await properties.

You're destroying your code readability and ruining the whole concept of OOP.

Properties are features not actions.

They don't do like methods. They are.

They are data holders representing states of an object.

Simple states:

class Person {
  firstName = 'Tari';
  lastName = 'Ibaba';
  site = 'codingbeautydev.com';
}

Derived states -- what getters are meant for:

class Person {
  firstName = 'Tari';
  lastName = 'Ibaba';
  site = 'codingbeautydev.com';

  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

const person = new Person();

console.log(person.fullName); // Tari Ibaba

But the status property was returning a Dart Future -- JavaScript's Promise equivalent:

❌Before:

class Permission {
  get status() {
    return new Promise((resolve) => {
      //
      resolve();
    });
  }
}

const notifications = new Permission();
await notifications.status;

It would have been so much better to use a method:

✅ After:

class Permission {
  getStatus() {
    return new Promise((resolve) => {
      //
      resolve();
    });
  }
}

const notifications = new Permission();
await notifications.getStatus();

And now async/await can make things even more intuitive:

class Permission {
  async getStatus() {
    //
  }
}

const notifications = new Permission();
await notifications.getStatus();

But guess what happens when you try async/await with properties?

Exactly. It's a property.

This rule doesn't just apply to async tasks, it applies to any long-running action, synchronous or not:

❌ Before:

class ActionTimer {
  constructor(action) {
    this.action = action;
  }

  // ❌ Property
  get time() {
    const then = Date.now();
    for (let i = 0; i < 1000000; i++) {
      this.action();
    }
    const now = Date.now();
    return now - then;
  }
}

const splice = () => [...Array(100)].splice(0, 10);

const actionTimer = new ActionTimer(splice);

console.log(`[email protected]: ${actionTimer.time}`);

✅ After:

Let them know that the action is expensive enough to deserve caching or variable assignment:

class ActionTimer {
  constructor(action) {
    this.action = action;
  }

  // ✅ Get method
  getTime() {
    const then = Date.now();
    for (let i = 0; i < 1000000; i++) {
      this.action();
    }
    const now = Date.now();
    return now - then;
  }
}

const splice = () => [...Array(100)].splice(0, 10);

const actionTimer = new ActionTimer(splice);

const theTime = actionTimer.getTime();
console.log(`[email protected]: ${theTime}`);

But sometimes it still doesn't deserve to be a property with this.

Check this out -- do you see the issue with the level setter property?

class Human {
  site = 'codingbeautydev.com';
  status = '';
  _fullness = 0;
  timesFull = 0;

  set fullness(value) {
    this._fullness = value;
    if (this._fullness <= 4) {
      this.status = 'hungry';
    } else if (this._fullness <= 7) {
      this.status = 'okay';
    } else {
      this.status = 'full';
      timesFull++;
    }
  }
}

const human = new Human();
human.fullness = 5;
console.log(`I am ${human.status}`);

It doesn't just modify the backing _fullness field -- it changes multiple other fields. This doesn't make sense as a property, as data.

It’s affecting so much aside from itself.

It has side-effects.

Setting this property multiple times modifies the object differently each time.

const human = new Human();

human.fullness = 8;

console.log(human.timesFull); // 1

human.fullness = 9;

console.log(human.timesFull); // 2

console.log(`I am ${human.status}`);

So even though it doesn't do much, it still needs to be a method.

class Human {
  site = 'codingbeautydev.com';
  status = '';
  _fullness = 0;

  setLevel(value) {
    this._fullness = value;
    if (this._fullness <= 3) {
      this.status = 'hungry';
    } else if (this._fullness <= 7) {
      this.status = 'okay';
    } else {
      this.status = 'full';
    }
  }
}

const human = new Human();
human.setLevel(5);
console.log(`I am ${human.status}`);

Name them right

Natural code. Coding like natural language.

So always name the properties with nouns like we've been doing here.

class ActionTimer {
  constructor(action) {
    this.action = action;
  }

  // ✅ Noun for property
  get time() {
    // ...
  }
}

But you see what we did when it was time to make it a property?

We made it a verb. Cause now it's an action that does something.

class ActionTimer {
  constructor(action) {
    this.action = action;
  }

  // ✅ verb phrase for method
  getTime() {
    // ...
  }
}

Nouns for entities: variables, properties, classes, objects, and more.

Not this

// ❌ do-examples.ts

// ❌ Cryptic
const f = 'Coding';
const l = 'Beauty';

// ❌ Verb
const makeFullName = `${f} ${l}`;

class Book {
  // ❌ Adjectival phrase
  createdAt: Date;
}

But this:

// ✅ examples.ts 

// ✅ Readable
const firstName = 'Coding';
const lastName = 'Beauty';

// ✅ Noun
const fullName = `${firstName} ${lastName}`;

class Book {
  // ✅ Noun phrase 
  dateCreated: Date;
}

Verbs for actions: functions and object methods.

Key points: when to use a method vs a property?

Use a property when:

  • The action modifies or returns only the backing field.
  • The action is simple and inexpensive.

Use a method when:

  • The action modifies multiple fields.
  • The action is async or expensive.

All for clean, readable, intuitive code.

Coding Beauty Assistant logo

Try Coding Beauty AI Assistant for VS Code

Meet the new intelligent assistant: tailored to optimize your work efficiency with lightning-fast code completions, intuitive AI chat + web search, reliable human expert help, and more.

See also