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
// Beforeclass Account { private _id = 0; setId(id: number): void { this._id = id; } get id(): number { return this._id; }}
const account = new Account();account.setId(42);
// Afterclass Account { constructor(private readonly _id: number) {} get id(): number { return this._id; }}
const account = new Account(42);# Beforeclass Account: def __init__(self): self._id = 0 def set_id(self, value): self._id = value @property def id(self): return self._id
account = Account()account.set_id(42)
# Afterclass Account: def __init__(self, id): self._id = id @property def id(self): return self._id
account = Account(42)// Set at construction, no setter exposed.type Account struct { id int // unexported, fixed at construction}
func NewAccount(id int) Account { return Account{id: id}}
func (a Account) ID() int { return a.id}
account := NewAccount(42)// Set at construction, no setter exposed.pub struct Account { id: u64, // private, no mutator}
impl Account { pub fn new(id: u64) -> Self { Account { id } }
pub fn id(&self) -> u64 { self.id }}
let account = Account::new(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 กลไกการทำงาน
หัวข้อที่มีชื่อว่า “กลไกการทำงาน”- ถ้า constructor ยังไม่รับ field นั้น ให้เพิ่มเป็น parameter แล้วกำหนดค่าข้างใน constructor
- อัปเดต caller ทุกรายที่สร้าง object ให้ส่งค่าผ่าน constructor แทนที่จะผ่าน setter รัน test หลังแต่ละราย
- ไล่หาการเรียก setter ที่ยังเหลือ ถ้า caller ยัง mutate field หลังสร้าง object ให้ตัดสินว่านั่นเป็นความต้องการจริงหรือไม่ ถ้าใช่ field นี้ก็ไม่เข้าข่ายท่านี้ ถ้าไม่ใช่ ก็ส่งค่าผ่าน constructor แทน
- เมื่อไม่เหลือ caller ที่เรียก setter แล้ว ก็ลบ setter ทิ้งได้
- ที่ภาษาอนุญาต ให้ทำเครื่องหมาย 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 ถูกเรียกครั้งเดียวทันทีหลัง construction | framework ต้องการ 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 รองรับ