# Refactoring 017 - Convert Attributes to Sets

> TL;DR: Using sets for attributes simplifies your code and makes state management easier

# Problems Addressed

- Mutability 
- Complexity
- [Attributes](https://maximilianocontieri.com/code-smell-34-too-many-attributes) become polluted  
- Setters  

# Related Code Smells

%[https://maximilianocontieri.com/code-smell-35-state-as-properties]

%[https://maximilianocontieri.com/code-smell-28-setters]

%[https://maximilianocontieri.com/code-smell-34-too-many-attributes]

# Steps

1. Identify attributes representing states
2. Replace the attributes with sets: one for each state
3. Adjust methods to move items between sets instead of mutating attributes

# Sample Code

## Before

[Gist Url]: # (https://gist.github.com/mcsee/18b119021e9a5c4e2340a72a0bd12978)

```typescript
class Bill {
  amount: number;
  paid: boolean;

  constructor(amount: number) {
    this.amount = amount;
    this.paid = false;
  }

  pay() {
    if (!this.paid) {
      this.paid = true;
    }
  }
}

const bill = new Bill(100);
console.log(bill.paid); // false
bill.pay();
console.log(bill.paid); // true
```

## After

[Gist Url]: # (https://gist.github.com/mcsee/f05f0411564b9a39697ebfa848e46995)

```typescript
// 1. Identify attributes representing states

class Accountant {  
   // 2. Replace the attributes with sets: one for each state
  unpaidBills: Set<Bill>;
  paidBills: Set<Bill>;

  constructor() {
    this.unpaidBills = new Set();
    this.paidBills = new Set();
  }

  addBill(bill: Bill) {
    this.unpaidBills.add(bill);
  }

  payBill(bill: Bill) {    
    // 3. Adjust methods to move items
    // between sets instead of mutating attributes
    if (this.unpaidBills.has(bill)) {
      this.unpaidBills.delete(bill);
      this.paidBills.add(bill);
    }
  }
}

class Bill {
  amount: number;

  constructor(amount: number) {
    this.amount = amount;
  }
}

const bill = new Bill(100);
const accountant = new Accountant();
accountant.addBill(bill);
console.log(accountant.unpaidBills.has(bill)); // true
accountant.payBill(bill);
console.log(accountant.paidBills.has(bill)); // true
```

# Type

[X] Semi-Automatic

# Safety

This refactoring is safe when your attributes don't rely on specific indexing behavior.

Since sets don't maintain element order, check if your logic depends on order.

# Why is the code better?

Entities are immutable in the essence.

Using sets ensures uniqueness and simplifies logic.

You no longer need to check for duplicates before adding elements.

Operations like union, intersection, and difference become straightforward, making your code more maintainable and flexible.

# Limitations

Sets don't preserve element order.

If your logic depends on sequence, converting to a set may not be appropriate and you should use an Ordered Collection or Array

# AI Refactoring

You can prompt your AI assistants to make this refactoring for you.

## Try Them!     

| Without Proper Instructions    | With Specific Instructions |
| -------- | ------- |
| [ChatGPT](https://chat.openai.com/?q=Correct+and+explain+this+code%3A+%60%60%60typescript%0D%0Aclass+Bill+%7B%0A++amount%3A+number%3B%0A++paid%3A+boolean%3B%0A%0A++constructor%28amount%3A+number%29+%7B%0A++++this.amount+%3D+amount%3B%0A++++this.paid+%3D+false%3B%0A++%7D%0A%0A++pay%28%29+%7B%0A++++if+%28%21this.paid%29+%7B%0A++++++this.paid+%3D+true%3B%0A++++%7D%0A++%7D%0A%7D%0A%0Aconst+bill+%3D+new+Bill%28100%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+false%0Abill.pay%28%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+true%0D%0A%60%60%60) | [ChatGPT](https://chat.openai.com/?q=Convert+internal+attributes+to+external+sets+usage%3A+%60%60%60typescript%0D%0Aclass+Bill+%7B%0A++amount%3A+number%3B%0A++paid%3A+boolean%3B%0A%0A++constructor%28amount%3A+number%29+%7B%0A++++this.amount+%3D+amount%3B%0A++++this.paid+%3D+false%3B%0A++%7D%0A%0A++pay%28%29+%7B%0A++++if+%28%21this.paid%29+%7B%0A++++++this.paid+%3D+true%3B%0A++++%7D%0A++%7D%0A%7D%0A%0Aconst+bill+%3D+new+Bill%28100%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+false%0Abill.pay%28%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+true%0D%0A%60%60%60) |
| [Claude](https://claude.ai/new?q=Correct+and+explain+this+code%3A+%60%60%60typescript%0D%0Aclass+Bill+%7B%0A++amount%3A+number%3B%0A++paid%3A+boolean%3B%0A%0A++constructor%28amount%3A+number%29+%7B%0A++++this.amount+%3D+amount%3B%0A++++this.paid+%3D+false%3B%0A++%7D%0A%0A++pay%28%29+%7B%0A++++if+%28%21this.paid%29+%7B%0A++++++this.paid+%3D+true%3B%0A++++%7D%0A++%7D%0A%7D%0A%0Aconst+bill+%3D+new+Bill%28100%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+false%0Abill.pay%28%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+true%0D%0A%60%60%60) | [Claude](https://claude.ai/new?q=Convert+internal+attributes+to+external+sets+usage%3A+%60%60%60typescript%0D%0Aclass+Bill+%7B%0A++amount%3A+number%3B%0A++paid%3A+boolean%3B%0A%0A++constructor%28amount%3A+number%29+%7B%0A++++this.amount+%3D+amount%3B%0A++++this.paid+%3D+false%3B%0A++%7D%0A%0A++pay%28%29+%7B%0A++++if+%28%21this.paid%29+%7B%0A++++++this.paid+%3D+true%3B%0A++++%7D%0A++%7D%0A%7D%0A%0Aconst+bill+%3D+new+Bill%28100%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+false%0Abill.pay%28%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+true%0D%0A%60%60%60) |
| [Perplexity](https://perplexity.ai/?q=Correct+and+explain+this+code%3A+%60%60%60typescript%0D%0Aclass+Bill+%7B%0A++amount%3A+number%3B%0A++paid%3A+boolean%3B%0A%0A++constructor%28amount%3A+number%29+%7B%0A++++this.amount+%3D+amount%3B%0A++++this.paid+%3D+false%3B%0A++%7D%0A%0A++pay%28%29+%7B%0A++++if+%28%21this.paid%29+%7B%0A++++++this.paid+%3D+true%3B%0A++++%7D%0A++%7D%0A%7D%0A%0Aconst+bill+%3D+new+Bill%28100%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+false%0Abill.pay%28%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+true%0D%0A%60%60%60) | [Perplexity](https://perplexity.ai/?q=Convert+internal+attributes+to+external+sets+usage%3A+%60%60%60typescript%0D%0Aclass+Bill+%7B%0A++amount%3A+number%3B%0A++paid%3A+boolean%3B%0A%0A++constructor%28amount%3A+number%29+%7B%0A++++this.amount+%3D+amount%3B%0A++++this.paid+%3D+false%3B%0A++%7D%0A%0A++pay%28%29+%7B%0A++++if+%28%21this.paid%29+%7B%0A++++++this.paid+%3D+true%3B%0A++++%7D%0A++%7D%0A%7D%0A%0Aconst+bill+%3D+new+Bill%28100%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+false%0Abill.pay%28%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+true%0D%0A%60%60%60) |
| [Copilot](https://www.bing.com/chat?showconv=1&sendquery=1&q=Correct+and+explain+this+code%3A+%60%60%60typescript%0D%0Aclass+Bill+%7B%0A++amount%3A+number%3B%0A++paid%3A+boolean%3B%0A%0A++constructor%28amount%3A+number%29+%7B%0A++++this.amount+%3D+amount%3B%0A++++this.paid+%3D+false%3B%0A++%7D%0A%0A++pay%28%29+%7B%0A++++if+%28%21this.paid%29+%7B%0A++++++this.paid+%3D+true%3B%0A++++%7D%0A++%7D%0A%7D%0A%0Aconst+bill+%3D+new+Bill%28100%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+false%0Abill.pay%28%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+true%0D%0A%60%60%60) | [Copilot](https://www.bing.com/chat?showconv=1&sendquery=1&q=Convert+internal+attributes+to+external+sets+usage%3A+%60%60%60typescript%0D%0Aclass+Bill+%7B%0A++amount%3A+number%3B%0A++paid%3A+boolean%3B%0A%0A++constructor%28amount%3A+number%29+%7B%0A++++this.amount+%3D+amount%3B%0A++++this.paid+%3D+false%3B%0A++%7D%0A%0A++pay%28%29+%7B%0A++++if+%28%21this.paid%29+%7B%0A++++++this.paid+%3D+true%3B%0A++++%7D%0A++%7D%0A%7D%0A%0Aconst+bill+%3D+new+Bill%28100%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+false%0Abill.pay%28%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+true%0D%0A%60%60%60) |
| [Gemini](https://gemini.google.com/?q=Correct+and+explain+this+code%3A+%60%60%60typescript%0D%0Aclass+Bill+%7B%0A++amount%3A+number%3B%0A++paid%3A+boolean%3B%0A%0A++constructor%28amount%3A+number%29+%7B%0A++++this.amount+%3D+amount%3B%0A++++this.paid+%3D+false%3B%0A++%7D%0A%0A++pay%28%29+%7B%0A++++if+%28%21this.paid%29+%7B%0A++++++this.paid+%3D+true%3B%0A++++%7D%0A++%7D%0A%7D%0A%0Aconst+bill+%3D+new+Bill%28100%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+false%0Abill.pay%28%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+true%0D%0A%60%60%60) | [Gemini](https://gemini.google.com/?q=Convert+internal+attributes+to+external+sets+usage%3A+%60%60%60typescript%0D%0Aclass+Bill+%7B%0A++amount%3A+number%3B%0A++paid%3A+boolean%3B%0A%0A++constructor%28amount%3A+number%29+%7B%0A++++this.amount+%3D+amount%3B%0A++++this.paid+%3D+false%3B%0A++%7D%0A%0A++pay%28%29+%7B%0A++++if+%28%21this.paid%29+%7B%0A++++++this.paid+%3D+true%3B%0A++++%7D%0A++%7D%0A%7D%0A%0Aconst+bill+%3D+new+Bill%28100%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+false%0Abill.pay%28%29%3B%0Aconsole.log%28bill.paid%29%3B+%2F%2F+true%0D%0A%60%60%60) |   

# Tags

- Mutability

# Related Refactorings

https://maximilianocontieri.com/refactoring-001-remove-setters

# See also

%[https://maximilianocontieri.com/the-evil-powers-of-mutants]

# Credits

Image by [Angelo Giordano](https://pixabay.com/users/angelo_giordano-753934/) in [Pixabay](https://pixabay.com/)

* * * 

This article is part of the Refactoring Series.

%[https://maximilianocontieri.com/how-to-improve-your-code-with-easy-refactorings]
