ข้ามไปยังเนื้อหา

Remove Setting Method

บาง field เป็นส่วนหนึ่งของ identity ของ object เช่น เลขบัญชี timestamp ตอนสร้าง หรือสกุลเงิน ค่าพวกนี้ควรถูกกำหนดตอน object เกิดแล้วไม่แตะอีกเลย ถ้า field แบบนั้นมี setter ใครก็ตามก็ mutate ทีหลังได้แบบเงียบ ๆ ให้ย้ายการกำหนดค่าเข้าไปใน constructor แล้ว ลบ setter ทิ้ง field จะได้ถูกตั้งครั้งเดียว และ object ก็ immutable ในแง่นั้น

คุณสร้าง object แล้วเรียก setter ทันทีเพื่อให้ค่าที่จะไม่มีวันเปลี่ยนอย่างถูกต้อง — new Account(); account.setId(42) การเกิดสองขั้นชวนให้บุคคลที่สามเรียก setter อีกครั้ง ห่างไกลจากการสร้าง และทำลาย identity ของ object setter ที่มีอยู่เพียงเพื่อให้ถูกเรียกครั้งเดียวตอนเริ่มคือ setter ที่ไม่ควรมีอยู่

Account เปิดเผย id ที่ตั้งค่าได้ เรารับ id ใน constructor และลบ setId ทำให้ identity ถาวร ใน Go และ Rust ไม่มี setter ตั้งแต่แรก — ประเด็นคือกำหนดค่าตอนสร้างและไม่เปิดเผยตัว mutate

// Before
class Account {
private _id = 0;
setId(id: number): void { this._id = id; }
get id(): number { return this._id; }
}
const account = new Account();
account.setId(42);
// After
class Account {
constructor(private readonly _id: number) {}
get id(): number { return this._id; }
}
const account = new Account(42);
flowchart LR
  subgraph Before["Before"]
    A["new Account()<br/>then setId(...)<br/>setId callable anytime"]
  end
  subgraph After["After"]
    B["new Account(id)<br/>id fixed forever<br/>no setId"]
  end
  Before -.->|"Remove Setting Method"| After
การสร้างสองขั้นที่มี setter กลายเป็นการสร้างครั้งเดียวที่ไม่เปลี่ยนแปลง
  1. ถ้า constructor ยังไม่รับ field นั้น ให้เพิ่มเป็น parameter แล้วกำหนดค่าข้างใน constructor
  2. อัปเดต caller ทุกรายที่สร้าง object ให้ส่งค่าผ่าน constructor แทนที่จะผ่าน setter รัน test หลังแต่ละราย
  3. ไล่หาการเรียก setter ที่ยังเหลือ ถ้า caller ยัง mutate field หลังสร้าง object ให้ตัดสินว่านั่นเป็นความต้องการจริงหรือไม่ ถ้าใช่ field นี้ก็ไม่เข้าข่ายท่านี้ ถ้าไม่ใช่ ก็ส่งค่าผ่าน constructor แทน
  4. เมื่อไม่เหลือ caller ที่เรียก setter แล้ว ก็ลบ setter ทิ้งได้
  5. ที่ภาษาอนุญาต ให้ทำเครื่องหมาย field เป็น readonly/final/private-ที่ไม่มีตัว mutate เพื่อให้ compiler บังคับความไม่เปลี่ยนแปลง

ใช้สิ่งนี้กับ field ที่นิยาม identity หรือตายตัวตลอดอายุของ object และกับ value object ที่คุณต้องการปฏิบัติว่าไม่เปลี่ยนแปลง เพื่อให้แชร์และ cache ได้อย่างอิสระ การลบ setter หดพื้นที่ผิวสำหรับ bug: ค่าที่เปลี่ยนไม่ได้ก็เปลี่ยนในเวลาที่ผิดไม่ได้

ข้อแลกเปลี่ยนคือแรงกดดันที่ constructor ถ้ามี field แบบนี้หลายตัว รายการ parameter ของ constructor อาจยาวขึ้น — นั่นเป็นสัญญาณให้ทำ Introduce Parameter Object หรือ builder ไม่ใช่เหตุผลที่จะคง setter ไว้ และ state ที่เปลี่ยนได้จริง ๆ — ยอดเงิน, สถานะ — ยังต้องมีวิธีเปลี่ยนที่ควบคุมได้; อย่าถอด setter ที่จำลองการเปลี่ยนแปลงจริงที่ดำเนินอยู่ เฟรมเวิร์กที่ต้องการ constructor ไม่มี argument และ field injection ก็อาจบังคับให้ต้องประนีประนอมตรงนี้

ใช้ Remove Setting Method เมื่อหลีกเลี่ยงเมื่อ
field กำหนด identity ของ object และไม่ควรเปลี่ยนfield จำเป็นต้องเปลี่ยนได้ตามอายุ object
setter ถูกเรียกครั้งเดียวทันทีหลัง constructionframework ต้องการ no-arg constructor + field injection
ต้องการ value object ที่แชร์และ cache ได้การ mutate นั้นคือ business operation ที่ถูกต้อง

⚠️ ไม่ควร Remove Setting Method เมื่อ:

  • field แทน state ที่เปลี่ยนได้ตามปกติ เช่น ยอดเงิน, สถานะออเดอร์
  • framework ที่ใช้อยู่ต้องการ setter สำหรับ deserialization หรือ DI
  • การลบ setter ทำให้ constructor มี parameter ยาวมากโดยไม่มี Parameter Object รองรับ
field แบบใดเป็นเป้าหมายที่ถูกต้องสำหรับ Remove Setting Method?
ค่าของ field ถูกกำหนดที่ไหนหลังการ refactor?
ประโยชน์หลักของการลบ setter คืออะไร?
ถ้าการลบ setter ทำให้ constructor มี parameter มากเกินไป อะไรคือการตอบสนองที่ถูกต้อง?