จะแก้ไขคุณสมบัติของวัตถุได้อย่างไร?
ผมเคยอ่านเมื่อเร็ว ๆ นี้เป็นบทความที่ดีเกี่ยวกับการทดสอบหน่วย มีตัวอย่างของวิธีการที่ไม่ดีซึ่งออกแบบมาไม่ดี หน้าตาเป็นแบบนี้
public static string GetTimeOfDay()
{
DateTime time = DateTime.Now;
if (time.Hour >= 0 && time.Hour < 6)
{
return "Night";
}
if (time.Hour >= 6 && time.Hour < 12)
{
return "Morning";
}
if (time.Hour >= 12 && time.Hour < 18)
{
return "Afternoon";
}
return "Evening";
}
มีบางสิ่งที่ผู้เขียนชี้ว่าเป็นรูปแบบการต่อต้าน:
- มันเชื่อมโยงอย่างแน่นหนากับแหล่งข้อมูลที่เป็นรูปธรรม (อ่านวันที่และเวลาปัจจุบันจากเครื่องที่ทำงาน)
- เป็นการละเมิดหลักการความรับผิดชอบเดียว (SRP)
- มันอยู่เกี่ยวกับข้อมูลที่จำเป็นในการทำงานให้ลุล่วง นักพัฒนาต้องอ่านซอร์สโค้ดจริงทุกบรรทัดเพื่อทำความเข้าใจว่าอินพุตที่ซ่อนอยู่ถูกใช้และมาจากที่ใด ลายเซ็นของวิธีการเพียงอย่างเดียวไม่เพียงพอที่จะเข้าใจพฤติกรรมของวิธีการ
ฉันเขียนโค้ดใน Python เป็นหลักและหลังจากบทความนี้ฉันรู้สึกว่าการใช้self
ในกรณีส่วนใหญ่ละเมิดจุดเหล่านั้นด้วย
class Car:
def __init__(self, power):
self.power = power
self.speed = 0
def accelerate(self, acceleration_time):
self.speed = self.calculate_acceleration(acceleration_time, self.power)
accelerate
มีอินพุตที่ซ่อนอยู่:self.power
- ลายเซ็นของวิธีการเพียงอย่างเดียวไม่เพียงพอที่จะเข้าใจพฤติกรรมของวิธีการ มีเอาต์พุตที่ซ่อนอยู่ (?)
self.speed
เป็นวิธีการเล็ก ๆ และอ่านง่าย แต่วิธีการที่มีหลายร้อยบรรทัดซึ่งอ่านและกำหนดให้self
ในหลาย ๆ ที่? หากผู้ที่ไม่ได้ตั้งชื่ออย่างถูกต้องนักพัฒนาจะมีปัญหาใหญ่ที่จะเข้าใจในสิ่งที่มันไม่และแม้ว่าผู้ที่มีชื่อถูกต้องนักพัฒนาควรอ่านทั้งการดำเนินการที่จะรู้ว่าถ้ามันไม่ปรับเปลี่ยนบางสิ่งหรือถ้าบริบทเพิ่มเติมจะถูกฉีดด้วยself
self
ในทางกลับกันเมื่อฉันพยายามโค้ดทุกวิธีโดยไม่ใช้self
ด้วยอินพุต (อาร์กิวเมนต์) และเอาต์พุต (ค่าส่งคืน) ฉันจะส่งตัวแปรหนึ่งตัวผ่านหลายวิธีและฉันจะทำซ้ำเอง
แล้วจะจัดการself
อย่างไรและใช้อย่างไรให้เหมาะสม? จะทำให้ชัดเจนได้อย่างไรว่าวิธีใดที่ใช้เป็นอินพุตและสิ่งที่ปรับเปลี่ยน (เอาต์พุต)
คำตอบ
ดีที่สุดคืออย่าให้มากเกินไป ใช่เป็นความจริงที่ว่าฟังก์ชันบริสุทธิ์ขนาดเล็กที่ไม่มีการไหลของข้อมูลที่ชัดเจนนั้นง่ายต่อการทดสอบมากกว่าการกลายพันธุ์ซึ่งส่งผลให้เกิดการกระทำบางอย่างในระยะ แต่ด้วยเหตุผลความไม่แน่นอนความไม่สมบูรณ์และการพึ่งพาไม่ได้เป็นปัญหา พวกเขาทำให้บางสิ่งสะดวกขึ้นมาก
ตามกฎทั่วไป: ยิ่งโค้ดบางตัวเข้าใกล้ตรรกะทางธุรกิจของซอฟต์แวร์บางตัวมากเท่าไหร่โค้ดก็จะยิ่งบริสุทธิ์ไม่เปลี่ยนรูปใช้งานได้ชัดเจนและทดสอบได้ ยิ่งโค้ดบางส่วนอยู่ใกล้กับเลเยอร์ด้านนอกของแอปพลิเคชันมากเท่าไหร่ฟังก์ชันการทำงานที่น้อยลงนั่นก็คุ้มค่ากับการทดสอบหน่วยอย่างรอบคอบและการออกแบบที่ทดสอบได้น้อยลงก็ไม่เป็นไร ตัวอย่างเช่นโค้ดที่ปิด API ภายนอกบางส่วนไม่สามารถทดสอบหน่วยได้อย่างสมเหตุสมผล
ตัวอย่างของปัญหาความไม่บริสุทธิ์การแนะนำการเขียนโปรแกรมจำนวนมากทำให้คุณสร้างอ็อบเจ็กต์โดเมนที่ให้ผลลัพธ์โดยตรง:
class Cat(Animal):
def make_noise(self):
print("meow")
นั่นไม่ใช่การออกแบบที่ดีเพราะเอาท์พุทควบคู่ไปกับsys.stdout
สตรีมอย่างแน่นหนา การออกแบบทดสอบเพิ่มเติมจะรวมถึงการกลับสตริงแทนการพิมพ์ได้โดยตรงเหมือนหรือผ่านในไฟล์ที่สามารถพิมพ์ไปที่:
def noise(self): return "meow"
def make_noise(self, stream): print("meow", file=stream)
car.accelerate(t)
ในตัวอย่างของคุณมีการดำเนินกรรมวิธี นี่ไม่ใช่ปัญหา! การดำเนินการนี้ไม่คุกคามความสามารถในการทดสอบเนื่องจากสามารถยืนยันผลลัพธ์ได้อย่างง่ายดาย:
car = Car(10)
assert car.speed == 0
car.accelerate(5)
assert car.speed == 50
ชื่อaccelerate()
นี้ยังทำให้ชัดเจนเพียงพอว่านี่คือการดำเนินการกลายพันธุ์ ภาษาอื่น ๆ ยังเข้ารหัสสิ่งนี้ในระบบประเภท (เช่นfn accelerate(&mut self)
ใน Rust) หรือในหลักการตั้งชื่อ (เช่นaccelerate!
ใน Ruby) การรักษาความแตกต่างระหว่างคำสั่งการกลายพันธุ์และการสืบค้นแบบบริสุทธิ์มักจะเป็นประโยชน์แม้ว่าจะไม่ได้ผลในทางปฏิบัติเสมอไป
หากมีปัญหาในโค้ดของคุณไม่ใช่ว่าวิธีการเร่ง () กำหนดให้self
แต่เป็นself.calculate_acceleration(time, self.power)
วิธีการ เมธอดนี้รับข้อมูลจากself
สองครั้ง: ครั้งหนึ่งเป็นอ็อบเจ็กต์ที่เมธอดเรียกใช้อีกครั้งผ่านพารามิเตอร์ที่สอง สิ่งนี้ทำให้การไหลของข้อมูลไม่โปร่งใส - ไม่มีเหตุผลที่จะเป็นวิธีการเว้นแต่self
จะกลายพันธุ์ภายในเมธอด การเปลี่ยนการออกแบบเช่นนี้จะมีประโยชน์:
def calculate_acceleration(time, power):
...
class Car:
def __init__(self, power):
...
def accelerate(self, acceleration_time):
self.speed = calculate_acceleration(acceleration_time, self.power)
ในกรณีนี้ไม่มีผลกระทบอย่างแท้จริงต่อความสามารถในการทดสอบ แต่ในกรณีอื่น ๆ อาจเป็นไปได้ที่จะทดสอบการคำนวณโดยตรงโดยไม่ต้องผ่านอินเทอร์เฟซของวัตถุ ในขณะที่วิธีการช่วยเหลือแบบคงที่ส่วนตัวในภาษาอื่นเป็นเรื่องปกตินั่นไม่ใช่แนวทางที่เหมาะสมสำหรับ Python เพียงแค่ใช้ฟังก์ชันฟรี
ข้อวิจารณ์ที่เป็นไปได้อย่างหนึ่งของวิธีการคือไม่ชัดเจนว่าจะใช้ช่องใด เช่นการไหลของข้อมูลประเภทนี้จะทำให้เกิดความเสียหายแม้ว่าเนื้อหาจะสอดคล้องกับ "Clean Code" ก็ตาม:
class ReallyWeirdObject:
def __init__(self, x, y):
self.x = x
self.y = y
self.z = None
self.use_x = False
def _helper(self):
self.z = self.x + self.y
def some_operation(self):
if self.use_x:
return self.x
else:
self._helper()
return 2 * self.z
weirdo = ReallyWeirdObject(1, 2)
weirdo.use_x = True
print(weirdo.some_operation())
แต่ WTF ในรหัสนี้คือการที่z
จะใช้ในการสื่อสารผลภายในหรือว่าเป็นเขตเมื่อมันมีแนวโน้มที่ควรจะมีข้อโต้แย้งคำหลักไม่จำเป็นที่จะuse_x
some_operation()
สิ่งที่ไม่ใช่ปัญหาคือการsome_operation()
ใช้ฟิลด์ของวัตถุที่ถูกเรียกใช้ นั่นก็เหมือนกับ ... ประเด็นทั้งหมด ตราบเท่าที่ข้อมูลในวัตถุนี้มีขนาดเล็กพอสมควรและสามารถจัดการได้การดำเนินการดังกล่าวก็ใช้ได้ หากคุณต้องการจินตนาการคุณสามารถเรียกสิ่งนี้ว่าอินสแตนซ์ของ "หลักการแยกส่วนต่อประสาน" ปัญหาส่วนใหญ่เกิดจากวัตถุเทพเจ้าที่เทอะทะจริง ๆ ซึ่งมีหลายสิบช่อง
คำถามไม่ควรเป็นว่าผู้เรียกภายนอกของเมธอดรู้ว่าจะใช้ฟิลด์ใดของวัตถุหรือไม่ ผู้เรียกไม่ควรต้องรู้เรื่องนี้วัตถุควรเป็นสิ่งที่ห่อหุ้มไว้ คำถามที่สำคัญกว่าคือการอ้างอิงและความสัมพันธ์เหล่านี้ชัดเจนจากภายในออบเจ็กต์หรือไม่ การมีหลายช่องแสดงถึงโอกาสมากมายที่สิ่งต่างๆจะไม่ตรงกัน
ประการแรกเป็นที่น่าสังเกตว่าตัวอย่างในบทความค่อนข้างมีการสร้างขึ้น (ด้วยเหตุผลในทางปฏิบัติ) และบริบทนั้นมีความสำคัญเมื่อพูดถึงสิ่งเหล่านี้ เช่นหากคุณกำลังเขียนเครื่องมือขนาดเล็กแบบใช้ครั้งเดียวมีเหตุผลเพียงเล็กน้อยที่จะรบกวนการออกแบบมากเกินไป แต่สมมติว่านี่เป็นส่วนหนึ่งของโครงการระยะยาวและคุณสามารถคาดหวังได้อย่างสมเหตุสมผลว่าโค้ดนี้จะได้รับประโยชน์จากการเปลี่ยนแปลงการออกแบบบางอย่าง (หรือคุณต้องใช้การเปลี่ยนแปลงที่ขัดแย้งกับการออกแบบปัจจุบันอยู่แล้ว) และมาตรวจสอบกัน ในบริบทนั้น
นี่คือรหัสสำหรับการอ้างอิง:
public static string GetTimeOfDay()
{
DateTime time = DateTime.Now;
if (time.Hour >= 0 && time.Hour < 6)
{
return "Night";
}
if (time.Hour >= 6 && time.Hour < 12)
{
return "Morning";
}
if (time.Hour >= 12 && time.Hour < 18)
{
return "Afternoon";
}
return "Evening";
}
ใน C # static
คำสำคัญโดยพื้นฐานแล้วหมายความว่านี่เป็นฟังก์ชันฟรี (กล่าวคือไม่ใช่วิธีการอินสแตนซ์บนวัตถุ) สิ่งนี้เกี่ยวข้องภายในบริบทของคำถามของคุณเนื่องจากคุณถามว่าข้อพิจารณาเหล่านี้ใช้กับวัตถุอย่างไร
ผู้เขียนบทความหยิบยกประเด็นต่างๆ ให้ฉันพูดก่อนที่ 1. (ควบคู่ไปกับการให้บริการวันที่ - DateTime
ชั้นเรียน) และ 3. (ทำให้เข้าใจผิดเกี่ยวกับการอ้างอิง) ปัญหานี้จะสร้างก็คือว่าในขณะที่ฟังก์ชั่นทำงานได้ดีภายใต้สถานการณ์ที่มันถูกสร้างขึ้นมาสำหรับใช้งานไม่ได้ในบริบทอื่น ๆ
เช่นจะเกิดอะไรขึ้นถ้าฉันต้องการสนับสนุน UI ที่ช่วยให้ผู้ใช้เห็นหมวดหมู่ "เวลาของวัน" สำหรับวันที่ในอนาคต (อีกครั้งตัวอย่าง "เช้า / บ่าย / เย็น / กลางคืน" นี้ได้รับการจัดทำขึ้น แต่สมมติว่ามันส่งคืนธุรกิจบางอย่าง หมวดหมู่ที่เกี่ยวข้องแทนสิ่งที่น่าสนใจสำหรับผู้ใช้)
บริบทดังกล่าวอีกประการหนึ่งคือการทดสอบซึ่งคุณต้องการให้สามารถเสียบค่าที่กำหนดไว้ล่วงหน้า (ไม่สามารถทำได้ในปัจจุบัน) และตรวจสอบผลลัพธ์ (จากมุมมองของการทดสอบฟังก์ชันนั้นไม่ได้กำหนด - คุณไม่สามารถบอกได้ คาดหวังอะไร).
สิ่งนี้แก้ไขได้อย่างง่ายดายโดยกำหนดให้วันที่ - เวลาเป็นพารามิเตอร์:
public static string GetTimeOfDay(DateTime dateTime)
{
// same code, except that it uses the dateTime param...
}
ตอนนี้เกี่ยวกับการละเมิด SRP (จุดที่ 2) - ปัญหาคือไม่มีความหมายมากนักที่จะพูดถึงเรื่องนี้ในแง่นามธรรม สิ่งที่ฉันหมายถึงก็คือมันไม่ได้มีความหมายมากนักเพียงแค่ดูโค้ดแยกกันและพิจารณาสถานการณ์ "จะเกิดอะไรขึ้น" แน่นอนว่ามีบางสิ่งทั่วไปที่คุณสามารถพูดเกี่ยวกับ SRP ได้ด้วยวิธีนี้ แต่ถ้าคุณไม่พิจารณาว่าโค้ดของคุณเปลี่ยนแปลงไปอย่างไรและความต้องการในการออกแบบที่แท้จริงคุณจะได้รับความพยายามที่สูญเปล่าและมากเกินไป ซับซ้อน (อ่านโค้ด "over-engineered")
ซึ่งหมายความว่าในขณะที่คุณสามารถและควรใช้ SRP ในขั้นต้นโดยอาศัยการคาดเดาที่มีการศึกษาและสมมติฐานที่สมเหตุสมผลคุณจะต้องพิจารณาการออกแบบของคุณใหม่เกี่ยวกับการทำซ้ำ / การวิ่งหลายครั้งเนื่องจากความเข้าใจในความรับผิดชอบที่แท้จริงและรูปแบบการเปลี่ยนแปลงจะเพิ่มขึ้นในขณะที่คุณทำงาน ในรหัสนี้
ตอนนี้ผู้เขียนบอกว่าฟังก์ชัน "ใช้ข้อมูลและประมวลผลด้วย" มันคลุมเครือเกินไปที่จะเป็นประโยชน์คุณสามารถพูดได้ว่าเกี่ยวกับฟังก์ชันใด ๆ และแม้ว่าฟังก์ชันจะมอบหมายการประมวลผลให้เป็นโค้ดระดับล่าง แต่ในตอนท้ายของห่วงโซ่จะต้องมีบางสิ่งที่ "ใช้ข้อมูลและประมวลผลด้วย"
สิ่งนี้คือถ้าส่วนนี้ของ codebase เปลี่ยนแปลงน้อยมาก (หรือไม่เคยเลย) คุณก็ไม่จำเป็นต้องพิจารณา SRP คุณอาจคิดด้วยเหตุผลหลายประการในการเปลี่ยนแปลง แต่หากการเปลี่ยนแปลงเหล่านั้นไม่เกิดขึ้นแสดงว่าคุณจ่ายค่าออกแบบโดยไม่ได้รับผลประโยชน์ใด ๆ เช่นบางทีสตริงที่ส่งคืนควรพร้อมใช้งานในภาษาอื่น (บางทีฟังก์ชันควรส่งคืนคีย์ไปยังพจนานุกรมบางตัวเพื่อรองรับการแปล) หรือบางทีค่าเกณฑ์สำหรับช่วงเวลาต่างๆของวันอาจแตกต่างกันไป - บางทีควรอ่านจากฐานข้อมูล หรือบางทีค่าเหล่านี้อาจเปลี่ยนแปลงตลอดทั้งปี หรือบางทีตรรกะทั้งหมดนี้อาจไม่เป็นสากลดังนั้นจึงควรใส่กลยุทธ์บางอย่างเข้าไปในฟังก์ชัน (รูปแบบกลยุทธ์) แล้วการออกแบบที่ต้องรองรับทั้งหมดข้างต้นล่ะ?
ดูว่าฉันหมายถึงอะไรจากสถานการณ์ "จะเกิดอะไรขึ้น" สิ่งที่คุณควรทำแทนคือพัฒนาความเข้าใจเกี่ยวกับโดเมนปัญหาและ codebase และใช้ SRP เพื่อให้แกนการเปลี่ยนแปลงที่โดดเด่นที่สุด (ประเภทของการเปลี่ยนแปลงความรับผิดชอบ) ได้รับการสนับสนุนอย่างดี
แนวคิดของรอยต่อ
ดังนั้นเมื่อคุณออกแบบฟังก์ชั่นหรือคลาส (หรือไลบรารีและเฟรมเวิร์กสำหรับเรื่องนั้น) คุณมักจะระบุจุดความสามารถในการขยาย - ตำแหน่งที่โค้ดไคลเอนต์สามารถเสียบบางสิ่งเข้าหรือมิฉะนั้นพารามิเตอร์ที่กำหนดไว้ Michael Feathers (ในการทำงานอย่างมีประสิทธิภาพกับ Legacy Code ) เรียกสิ่งเหล่านี้ว่า "ตะเข็บ" - ตะเข็บเป็นสถานที่ที่คุณสามารถเชื่อมต่อส่วนประกอบซอฟต์แวร์สองชิ้นเข้าด้วยกัน การทำให้วันที่และเวลาเป็นพารามิเตอร์เป็นตะเข็บที่ง่ายมาก การฉีดแบบพึ่งพายังเป็นวิธีการสร้างตะเข็บ เช่นคุณสามารถฉีดฟังก์ชันหรืออ็อบเจกต์ที่สามารถส่งคืนอินสแตนซ์วันที่และเวลาได้ด้วย (ซึ่งอาจเป็นการ overkill ในบริบทของตัวอย่างนี้หรือไม่ก็ได้)
แล้ววัตถุล่ะ?
จนถึงตอนนี้เราได้พิจารณาสิ่งต่างๆในระดับของฟังก์ชันฟรี วัตถุให้ระดับองค์กรอื่น ตอนนี้คุณต้องพิจารณาวัตถุโดยรวมเนื่องจากวัตถุมีกลไกของตัวเองในการแนะนำตะเข็บ
วิธีทั่วไปในการทำเช่นนี้คือการฉีดคอนสตรัคเตอร์ (ซึ่งส่งผลให้ได้วัตถุที่พร้อมใช้งาน) 1 . คลาส (Python) ที่เทียบเท่ากับโค้ดตัวอย่างด้านบนจะเป็น:
class DateTimeServices:
def __init__(self):
self.datetime = datetime; # from datetime import datetime
def get_time_of_day(self):
now = self.datetime.now()
if 0 <= now.hour < 6:
return "Night"
if 6 <= now.hour < 12:
return "Morning"
if 12 <= now.hour < 18:
return "Afternoon"
return "Evening"
สิ่งนี้มีปัญหาเหมือนกัน แต่ตอนนี้ปัญหาไม่ใช่วิธีการเองเป็นความจริงที่ว่าตัวสร้างคลาสสร้างการอ้างอิงวันที่และเวลาภายในและไม่ได้เสนอวิธีที่ชัดเจนในการเสียบอย่างอื่น ไม่มีตะเข็บในตัวเพื่อจุดประสงค์นี้ ไม่ใช่เรื่องง่ายที่จะนำคลาสกลับมาใช้ใหม่ในสถานการณ์อื่น
นี่คือคลาสเดียวกัน แต่ตอนนี้ตัวสร้างใช้ "datetime provider":
class DateTimeServices:
def __init__(self, datetimeProvider):
self.datetimeProvider = datetimeProvider;
def get_time_of_day(self):
now = self.datetimeProvider.now()
if 0 <= now.hour < 6:
return "Night"
if 6 <= now.hour < 12:
return "Morning"
if 12 <= now.hour < 18:
return "Afternoon"
return "Evening"
# elsewhere:
dts = DateTimeServices(datetime)
dts.get_time_of_day()
ตอนนี้คุณสามารถเสียบสิ่งต่างๆได้ตราบเท่าที่สิ่งที่มีบทบาทdatetimeProvider
เป็นไปตามอินเทอร์เฟซที่ต้องการ (ซึ่งในกรณีนี้ประกอบด้วยเมธอด now () เท่านั้นที่ส่งคืนอินสแตนซ์วันที่และเวลา) เช่น:
class FakeDateTimeProvider:
def __init__(self, year, month, day, hour, minute = 0, second = 0):
self.datetime = datetime(year, month, day, hour, minute, second)
def now(self):
return self.datetime
# then:
dts = DateTimeServices(FakeDateTimeProvider(2020, 8, 18, 8))
dts.get_time_of_day()
# always returns "Morning"
สิ่งนี้กล่าวถึงข้อกังวล 1. & 3. จากก่อนหน้านี้ (ด้วยข้อพิจารณาเดียวกันเกี่ยวกับข้อกังวล 2. (SRP)) คุณจะเห็นว่าการใช้self
ไม่ได้เป็นปัญหาในตัวมันเอง แต่เกี่ยวข้องกับการออกแบบคลาสมากกว่า ดังที่คำตอบอื่น ๆ ได้กล่าวไว้เมื่อคุณใช้คลาส (หรืออย่างแม่นยำกว่านั้นก็คืออ็อบเจกต์) คุณจะรู้ว่าอ็อบเจ็กต์นั้นแสดงถึงแนวคิดอะไรและไม่น่าแปลกใจสำหรับคุณโปรแกรมเมอร์ที่คลาสมีและใช้สถานะภายในของมัน
class Car:
def __init__(self, power):
self.power = power
self.speed = 0
def accelerate(self, acceleration_time):
self.speed = self.calculate_acceleration(acceleration_time, self.power)
จากความเข้าใจของฉันเกี่ยวกับคลาส Car จากการตั้งชื่อวิธีการและบางทีอาจมาจากเอกสารประกอบก็ไม่น่าแปลกใจสำหรับฉันที่accelerate
เปลี่ยนสถานะของอินสแตนซ์ นี่ไม่ใช่สิ่งที่คาดไม่ถึงสำหรับออบเจ็กต์
สิ่งที่เป็นปัญหาคือถ้าชั้นเรียนมีการอ้างอิงที่ซ่อนอยู่ซึ่งเกี่ยวข้องกับงานของคุณทำให้สิ่งต่างๆยากขึ้นสำหรับคุณ
ที่กล่าวว่าสิ่งที่อาจทำให้สับสน (ในแง่ของข้างต้น) คือบ่อยครั้งที่วิธีการอินสแตนซ์จำเป็นต้องใช้พารามิเตอร์ของตัวเอง คิดว่าสิ่งเหล่านี้เป็นการยอมรับข้อมูลบริบทเพิ่มเติมที่ไม่เกี่ยวข้องโดยตรงกับความรับผิดชอบหลักของชั้นเรียน เช่นไม่ใช่สิ่งที่คุณสามารถส่งต่อไปยังผู้สร้างได้เพียงครั้งเดียว แต่เป็นสิ่งที่อาจเปลี่ยนแปลงได้ในทุกครั้งที่โทร ตัวอย่างของเล่นคลาสสิกอย่างหนึ่งคือรูปทรง (วงกลมสามเหลี่ยมสี่เหลี่ยม) ที่สามารถวาดเองได้ (หรือแทนที่จะเป็นรูปร่างสิ่งเหล่านี้อาจเป็นองค์ประกอบ UI (ปุ่มป้ายกำกับ ฯลฯ ) หรือเอนทิตีเกม (เช่นสไปรต์ 2 มิติ)) วิธีหนึ่งที่จะทำได้คือมีวิธีการวาดแบบไม่มีพารามิเตอร์ () ซึ่งจะทำการวาดทั้งหมดภายใน แต่ถ้าคุณต้องการวาดสิ่งเดียวกันในส่วนที่แตกต่างกันอย่างสิ้นเชิงของ UI บนพื้นผิวการวาดที่แยกต่างหากล่ะ? หรือบนบัฟเฟอร์อื่นเพื่อให้คุณสามารถทำเอฟเฟกต์พิเศษเช่นพอร์ทัลหรือมิเรอร์ได้? ทางเลือกที่ยืดหยุ่นกว่าคือการส่งผ่านพื้นผิวการวาด (หรือวัตถุกราฟิกบางชนิด) เป็นพารามิเตอร์ของวิธีการวาด
แต่วิธีการที่มีหลายร้อยบรรทัดที่อ่านและกำหนดให้ตัวเองในหลาย ๆ ที่?
เอารหัสนั้นเผาด้วยไฟ
หากผู้พัฒนาเหล่านั้นไม่ได้รับการตั้งชื่ออย่างถูกต้องจะมีปัญหาใหญ่ในการทำความเข้าใจว่ามันทำอะไรและแม้ว่าผู้พัฒนาเหล่านั้นจะได้รับการตั้งชื่ออย่างถูกต้องก็ตามควรอ่านการใช้งานทั้งหมดเพื่อให้ทราบว่ามีการปรับเปลี่ยนเนื้อหาบางอย่างหรือไม่หรือบริบทเพิ่มเติมถูกแทรกเข้าไปด้วยตนเอง
ใช่. ตรง อย่าเขียนวิธีการด้วยรหัสหลายร้อยบรรทัด
ตอนนี้ในบันทึกที่จริงจังกว่านั้นบางครั้งคุณจะจบลงด้วยวิธีการใหญ่ ๆ แต่ส่วนใหญ่แล้วให้พยายามแยกย่อยรหัสของคุณให้เป็นวิธีการเล็ก ๆ และชั้นเรียนขนาดเล็ก
หากคุณมีวิธีการขนาดใหญ่เช่นเดียวกับที่คุณอธิบายวิธีหนึ่งที่คุณไม่สามารถสร้างหัวหรือก้อยได้วิธีการนั้นจะประสบปัญหาการออกแบบทุกประเภทที่คุณจะไม่สามารถแก้ไขได้โดยการเปลี่ยนลายเซ็น มันไม่เกี่ยวกับself
หรือเกี่ยวกับสิ่งที่พารามิเตอร์ที่จะ - วิธีการนี้มีปัญหาที่ใหญ่กว่า คุณต้องปรับโครงสร้างใหม่ค้นหาสิ่งที่เข้าใจได้ทั่วไปและแยกย่อยออกเป็นชิ้นเล็ก ๆ ที่เข้าใจได้มากขึ้นและเชื่อถือได้มากขึ้น (วิธีการที่คุณไม่ต้องพิจารณาเพื่อที่จะเข้าใจวิธีการที่เรียกมัน) คุณอาจลงเอยด้วยการจัดเรียงชิ้นส่วนเหล่านั้นในชั้นเรียนที่แตกต่างกันโดยสิ้นเชิง
ในทางกลับกันเมื่อฉันจะพยายามเขียนโค้ดทุกวิธีโดยไม่ใช้ตัวเองด้วยอินพุต (อาร์กิวเมนต์) และเอาต์พุต (ค่าส่งคืน) ฉันจะส่งตัวแปรหนึ่งตัวผ่านหลายวิธีและฉันจะทำซ้ำเอง
ดีอย่าไปสุดขั้วอย่างใดอย่างหนึ่ง เขียนคลาสที่ค่อนข้างเล็กพยายามค้นหานามธรรมที่เป็นประโยชน์และพิจารณาสิ่งที่คุณส่งผ่านเป็นพารามิเตอร์ / การพึ่งพาของอ็อบเจ็กต์และสิ่งที่คุณต้องการให้เป็นข้อมูลเชิงบริบทสำหรับแต่ละวิธี พิจารณาว่าอินสแตนซ์ของชั้นเรียนของคุณควรปรากฏในสถานการณ์อื่นนอกเหนือจากที่คุณตั้งใจไว้ในตอนแรกหรือไม่และดูว่าการออกแบบของคุณสามารถรองรับได้หรือไม่
จะทำให้ชัดเจนได้อย่างไรว่าวิธีใดที่ใช้เป็นอินพุตและสิ่งที่ปรับเปลี่ยน (เอาต์พุต)
อีกครั้งเมื่อพูดถึงออบเจ็กต์สิ่งที่คุณต้องการก็คือทำให้ชัดเจนว่าวัตถุนั้นหมายถึงอะไร สำหรับการอ้างอิงระดับออบเจ็กต์ให้ใช้การฉีดคอนสตรัคเตอร์ (โดยเฉพาะอย่างยิ่ง) และทำให้ชัดเจนว่าคลาสนั้นแสดงถึงอะไรในแนวความคิดสิ่งที่ทำและวิธีการใช้ ตัวอย่างเช่นใช้การตั้งชื่อที่ดีอธิบายสิ่งที่พวกเขาทำและใช้ประโยชน์จากพารามิเตอร์ตามบริบทเมื่อจำเป็น สำหรับวิธีการคลาสและวิธีการแบบคงที่ให้คุกคามพวกเขามากขึ้นในฐานะฟังก์ชันฟรีที่เกี่ยวข้องอย่างใกล้ชิดกับแนวคิดที่แสดงโดยคลาสที่มีอยู่ (ซึ่งมักเป็นสิ่งต่างๆเช่นวิธีการช่วยเหลือและโรงงาน)
1บางครั้งการฉีดคอนสตรัคเตอร์ไม่สามารถทำได้ (เช่นเฟรมเวิร์กอาจต้องใช้คอนสตรัคเตอร์แบบไม่มีพารามิเตอร์) ดังนั้นการพึ่งพาจึงถูกฉีดผ่านวิธีการหรือคุณสมบัติแทน แต่ก็ไม่เหมาะอย่างยิ่ง
โดยปกติคำถามประเภทนี้สามารถตอบได้โดยดูที่รหัสโดยใช้วิธีการ
acceleration_time = 5000 # in milliseconds
car.accelerate(acceleration_time)
print(car.speed) # <-- what do you as a programmer expect the speed to be?
ในขณะที่เราต้องการที่จะเขียนรหัสทดสอบเราทำใช้รหัสด้านนอกของการทดสอบหน่วย การทดสอบหน่วยตรวจสอบพฤติกรรมการเผชิญหน้ากับสาธารณชน ลักษณะการทำงานภายในของชั้นไม่ได้เป็นสิ่งทดสอบหน่วยความต้องการที่จะตรวจสอบอย่างชัดเจน
เมื่อฉันเห็นคำว่า "เร่ง" ฉันคาดว่าบางอย่างจะเร็วขึ้นหลังจากการเร่งความเร็วเสร็จสิ้น นี่หมายถึงการเปลี่ยนแปลงค่ารันไทม์ของself.speed
.
ตรงกันข้ามกับฟิสิกส์การสร้างแบบจำลองชั้นเรียนเช่นVehicleAccelerationPhysics
. ฉันคาดหวังว่าcalculate_acceleration
เมธอดจะคืนค่าไม่ใช่แก้ไขค่า แต่accelerate
วิธีการหนึ่งCar
จะไม่ทำให้ฉันแปลกใจหากcar.speed
มีการเปลี่ยนแปลง - ฉันคาดหวังว่ามันจะเปลี่ยนไป
ดังนั้นโค้ดของคุณจึงไม่ละเมิดแนวทางปฏิบัติที่ดีที่สุดเท่าที่เกี่ยวข้องกับการทดสอบหน่วย
accelerate
มีอินพุตที่ซ่อนอยู่:self.power
ค่าปัจจุบันของself.power
คือรายละเอียดการใช้งานไม่ใช่ "อินพุตที่ซ่อนอยู่" หากคุณต้องการเร่งความเร็วที่เฉพาะเจาะจง แต่Car
ชั้นเรียนของคุณต้องการaccelerate_to_speed
วิธีที่คำนวณเวลาเร่งความเร็วที่เหมาะสมตามกำลังของรถในปัจจุบัน
ลายเซ็นของวิธีการเพียงอย่างเดียวไม่เพียงพอที่จะเข้าใจพฤติกรรมของวิธีการ
ดูเหมือนว่าฉันจะพบ รถสามารถเร่งความเร็วได้ หลังจากเร่งความเร็วได้มากกว่าเดิม นั่นคือทั้งหมดที่ฉันต้องรู้
วิธีการพื้นฐานคือการใส่ตรรกะให้มากที่สุดเท่าที่จะเป็นไปได้ในฟังก์ชันที่อยู่นอกคลาส (หรือเป็นแบบคงที่) จากนั้นเรียกมันอย่างรัดกุมในวิธีการที่ขึ้นอยู่กับสถานะ (การเรียกเหล่านี้ยังคงจำเป็นต้องซ่อนคุณสมบัติที่ส่งผ่านในทางเทคนิคจากลายเซ็นของพวกเขา แต่นั่นเป็นจุดสำคัญของ OOP เพื่อให้มีสถานะถาวรแยกจากวิธีการอื่นที่ต้องการพวกเขาไม่ได้เป็นเพียงฟังก์ชันในเครื่องดูดฝุ่น ) ประเด็นหลักอื่น ๆ ที่ฉันต้องการให้ทำคือมีปัญหาอื่น ๆ ที่เราควรแก้ไขก่อน
ด้วยตัวอย่างแรกของคุณจะช่วยแก้ไขก่อนเพื่อแก้ไขข้อกังวลอื่น ๆ ซึ่งเป็นการยากที่จะทดสอบหน่วย ตามหลักการแล้วเราต้องการบางสิ่งเช่น
public static string GetTimeOfDay() => get_time_of_day(DateTime.Now.Hour);
// Helper function that's easy to unit test, & can live outside a class
public static get_time_of_day(hour)
{
if (hour >= 0 && hour < 6)
return "Night";
if (hour >= 6 && hour < 12)
return "Morning";
if (hour >= 12 && hour < 18)
return "Afternoon";
return "Evening";
}
แนวทางนี้ยังคงตกอยู่ในความผิดพลาดของการวิพากษ์วิจารณ์อย่างคับคั่ง แต่เราสามารถแก้ไขได้โดยการให้GetTimeOfDay
อาร์กิวเมนต์ซึ่งฉันได้ทำทางเลือกในตัวอย่างด้านล่าง:
public static string GetTimeOfDay(DateTime now=DateTime.Now) => get_time_of_day(now.Hour);
ในตัวอย่างที่สองฉันจะเปลี่ยนpower
คำศัพท์ของคุณ accelerate
วิธีแปลกในการที่จะผ่านคุณสมบัติของเช่นชั้นเป็นวิธีที่เพราะมันมีชีวิตที่ไม่ใช่แบบคงที่ในชั้นเรียนที่สามารถเรียกทรัพย์สินที่อยู่แล้วเช่นถ้ามันเป็นลูกผสมระหว่างหลบซ่อนตัวอยู่สองสายดังกล่าวและหลบซ่อนตัวที่ไม่ใช่ของพวกเขา . สามารถเปลี่ยนแปลงได้ดังนี้:
class Car:
def __init__(self, acceleration):
self.acceleration = acceleration
self.speed = 0
def accelerate(self, acceleration_time):
self.speed += acceleration_time*self.acceleration
ซึ่งง่ายต่อการทดสอบเช่น
car = Car(3)
car.accelerate(4)
assert car.speed == 12
(อย่าลังเลที่จะฟอร์แมตใหม่ตามที่คุณต้องการ) แต่ก็ยังขึ้นอยู่กับself.acceleration
ดังนั้นคุณอาจต้องการเช่น
def accelerate(self, acceleration_time):
self.speed += delta_speed(self.acceleration, acceleration_time)
def delta_speed(acceleration, acceleration_time): return acceleration*acceleration_time
หมายเหตุdelta_speed
อยู่ในระดับการเยื้องเดียวกันCar
เนื่องจากไม่ได้อยู่ในคลาสดังนั้นจึงไม่มีพารามิเตอร์ที่ซ่อนอยู่ที่รบกวนคุณ (ในฐานะที่เป็นแบบฝึกหัดคุณอาจเขียนวิธีการนี้ขึ้นใหม่เพื่อใช้=
แทน+=
ซึ่งไม่เกี่ยวข้องกับประเด็นที่ทำไว้ที่นี่)
ข้อสังเกตของคุณมีความถูกต้อง (ถ้าไม่ใช่ส่วนใหญ่) แต่ข้อสรุปที่คุณได้รับจากข้อสรุปนั้นมากเกินไป
- มันเชื่อมโยงอย่างแน่นหนากับแหล่งข้อมูลที่เป็นรูปธรรม (อ่านวันที่และเวลาปัจจุบันจากเครื่องที่ทำงาน)
แก้ไข. ควรส่งค่าวันที่เป็นพารามิเตอร์หรือควรใส่ค่าอ้างอิงแบบนาฬิกา
โปรดทราบว่าการฉีดแบบพึ่งพาต้องใช้คลาสและวิธีการที่ไม่คงที่ เพิ่มเติมในภายหลัง
รับทราบคำแนะนำหลัง (การฉีดการพึ่งพา) คำถามของคุณโต้แย้งกับแนวคิดนี้และนั่นคือสิ่งที่การสังเกตของคุณหลุดออกไปจากราง เพิ่มเติมในภายหลัง
- เป็นการละเมิดหลักการความรับผิดชอบเดียว (SRP)
ฉันไม่เห็นว่ามันเป็นอย่างไรและคุณไม่ได้เหตุผลว่าทำไมคุณถึงคิดว่ามันเป็นเช่นนั้น วิธีนี้ทำได้อย่างหนึ่ง SRP ไม่ได้เน้นว่าจะมีการฉีดการอ้างอิงหรือไม่ SRP มุ่งเน้นไปที่ตรรกะที่มีอยู่ภายในคลาส คลาสนี้มีจุดประสงค์ที่กำหนดไว้อย่างเคร่งครัดคือสร้างป้ายกำกับที่เป็นมิตรกับมนุษย์สำหรับช่วงเวลาปัจจุบันของวัน
เพื่อความชัดเจน: โค้ดสามารถปรับปรุงได้ แต่ SRP ไม่ใช่สิ่งที่ควรคำนึงถึงว่าเป็นการละเมิดที่นี่
อาร์กิวเมนต์ที่เรียกค่าวันที่และเวลาเป็นความรับผิดชอบที่ไม่ต่อเนื่องเป็นอาร์กิวเมนต์ที่ยากลำบาก ความรับผิดชอบใด ๆสามารถแบ่งย่อยออกเป็นความรับผิดชอบที่เล็กกว่าได้ - แต่มีเส้นคั่นระหว่างสิ่งที่สมเหตุสมผลและสิ่งที่เกินความสามารถ สมมติว่าวิธีนี้บ่งบอกว่าเวลาปัจจุบันของวันกำลังได้รับการประเมินนี่ไม่ใช่การละเมิด SRP
- มันอยู่เกี่ยวกับข้อมูลที่จำเป็นในการทำงานให้ลุล่วง นักพัฒนาต้องอ่านซอร์สโค้ดจริงทุกบรรทัดเพื่อทำความเข้าใจว่าอินพุตที่ซ่อนอยู่ใช้อะไร ...
ที่เถียงได้ เมื่อฉันเห็นGetTimeOfDay
และไม่ได้ใช้ค่าวันที่และเวลาอย่างชัดเจน (ไม่ว่าจะเป็นพารามิเตอร์วิธีการหรือการอ้างอิง) ดังนั้นการอนุมานเชิงตรรกะก็คือเวลาปัจจุบันกำลังถูกใช้
แม้ในเชิงความหมาย "การรับเวลาของวัน" แสดงให้เห็นว่าคุณได้รับเวลาปัจจุบันดังนั้นฉันจึงไม่ค่อยเห็นปัญหาเกี่ยวกับการตั้งชื่อที่นี่
... และมาจากไหน ...
สิ่งนี้ฉันเห็นด้วย คุณไม่รู้ว่ามันขึ้นอยู่กับนาฬิการะบบหรือ API บนคลาวด์หรือ ... สิ่งนี้จะแก้ไขได้เมื่อคุณฉีดเป็นการพึ่งพาหรือเพิ่มเป็นพารามิเตอร์วิธีการ
ลายเซ็นของวิธีการเพียงอย่างเดียวไม่เพียงพอที่จะเข้าใจพฤติกรรมของวิธีการ
หลักการ OOP ส่วนใหญ่ (SOLID ในหมู่คนอื่น ๆ ) มุ่งเน้นไปที่ชั้นเรียนไม่ใช่วิธีการ คุณไม่ควรสังเกตวิธีการด้วยตนเองคุณควรมองว่าเป็นการดำเนินการในชั้นเรียนและโดยเฉพาะอย่างยิ่งในอินสแตนซ์ที่รู้จักกันดีของคลาสนั้น
เท่าที่เกี่ยวข้องกับความสามารถในการอ่านโค้ดคุณอาจสันนิษฐานได้ว่าใครก็ตามที่เรียกใช้เมธอดคลาสบนอินสแตนซ์ (อ็อบเจ็กต์) ของคลาสนั้นก็ตระหนักถึงวิธีการสร้างอ็อบเจ็กต์นั้นตั้งแต่แรก นั่นไม่ใช่กรณีเสมอไป แต่เมื่อไม่เป็นเช่นนั้นหมายความว่าผู้โทรยินยอมที่จะมอบหมายการสร้างวัตถุ
นั่นไม่ใช่ความรับผิดชอบของคุณ (คุณ = ผู้ออกแบบคลาสที่สิ้นเปลือง) คุณไม่สามารถและไม่ควรพยายามจัดการวิธีที่ผู้บริโภคของคุณมอบหมายงานของตนเองภายใน
เมื่อแหล่งที่มาของค่าวันที่และเวลาได้รับการ refactored ให้เป็นการแทรกซึมหรือพารามิเตอร์วิธีการปัญหาที่ระบุในสัญลักษณ์แสดงหัวข้อย่อยที่สามของคุณจะเป็นโมฆะ
แล้วจะจัดการยัง
self
ไง ... ?
"จัดการกับ" หมายความว่าเป็นปัญหาหรือรายการที่ไม่ต้องการ วาทกรรมของคุณself
และประเด็นที่ถูกกล่าวหาเกี่ยวกับเรื่องนี้ถือเป็นการไม่ชอบแนวคิดเรื่องรัฐเชิงวัตถุ
ถ้าคุณรู้สึกแบบนั้นและไม่อยากเปลี่ยนวิธีคิดก็ไม่เป็นไรเช่นกัน การเขียนโปรแกรมเป็นแนวคิดเชิงนามธรรมของจิตใจและมีวิธีการที่แตกต่างกันเพื่อแก้ปัญหาเดียวกัน ในกรณีนี้คุณควรพิจารณาย้ายไปใช้การเขียนโปรแกรมเชิงฟังก์ชันแทนการเขียนโปรแกรมเชิงวัตถุด้วยเหตุผลสำคัญประการหนึ่ง:
self
ที่เป็นหัวใจของ OOP
สถานะการติดตามวัตถุ นั่นคือสิ่งที่พวกเขาทำ หากไม่เป็นเช่นนั้นโค้ดเบสของคุณจะมีเฉพาะเมธอดเท่านั้นจากนั้นเมธอดเหล่านั้นทั้งหมดสามารถทำให้เป็นแบบคงที่ได้
self
คือคีย์เวิร์ดที่ช่วยให้คุณเข้าถึงสถานะของอ็อบเจ็กต์ปัจจุบัน หากไม่มีself
คุณจะไม่สามารถจัดเก็บและเรียกคืนสถานะวัตถุได้อย่างมีประสิทธิภาพดังนั้นเราจะเปลี่ยนกลับไปใช้ระบบที่ทุกอย่างเป็นเพียงชุดของวิธีการคงที่
หมายเหตุ: ในคำถามของคุณคุณได้แจ้งว่าคุณตัดสินแต่ละวิธีทีละวิธี ซึ่งจริงๆแล้วสอดคล้องกับวิธีการทำงานของคุณด้วยวิธีการแบบคงที่ แต่ไม่เข้ากันได้กับวิธีที่คุณควรคิดเกี่ยวกับโค้ดเชิงวัตถุ
... และใช้อย่างไรให้ถูกวิธี?
สิ่งนี้กลับไปที่ส่วนที่ฉันบอกว่าคุณต้องสังเกตสิ่งต่างๆในระดับชั้นเรียนไม่ใช่ที่ระดับวิธีการ
วิธีที่ง่ายที่สุดในการคิดก็คือสถานะที่เก็บไว้ในอ็อบเจ็กต์ (กล่าวคือโดยself
ปกติจะทำผ่านตัวสร้าง) ได้รับการกำหนดค่าเพียงครั้งเดียวและสามารถเข้าถึงได้โดยวิธีการทั้งหมดของคลาสนั้น ตัวอย่างเช่น:
public class Clock
{
public DateTime GetDateTime()
{
return DateTime.Now;
}
}
public class SundayChecker
{
private Clock clock;
public SundayChecker(Clock clock)
{
this.clock = clock;
}
public bool IsItSunday()
{
var now = this.clock.GetDateTime();
return now.DayOfWeek == DayOfWeek.Sunday;
}
}
สังเกตว่าฉันต้องบอกSundayChecker
นาฬิกาเรือนไหนที่ควรใช้เพียงครั้งเดียวแต่ฉันสามารถตรวจสอบเวลาปัจจุบันซ้ำ ๆและยืนยันได้ว่าเป็นวันอาทิตย์หรือไม่
นี่เป็นเพียงตัวอย่างง่ายๆ แต่แสดงให้เห็นถึงลักษณะพื้นฐานของ OOP
หมายเหตุ: มีข้อโต้แย้งอีกมากมายที่สนับสนุนการใช้สถานะวัตถุ แต่นี่เป็นข้อโต้แย้งที่ง่ายที่สุดในการเข้าใจเพื่อเปลี่ยนความคิดของคุณไปสู่กรอบที่เข้ากันได้กับ OOP
สิ่งนี้กว้างเกินไปสำหรับคำอธิบายเชิงลึกเกี่ยวกับ OOP และควรใช้อย่างไร ฉันขอแนะนำให้คุณค้นคว้าแบบฝึกหัด OOP และแบบฝึกหัดที่สอนให้คุณใช้ (และในทางกลับกันก็ต้องรู้วิธีใช้ประโยชน์) โค้ดเชิงวัตถุ
เป็นวิธีการเล็ก ๆ และอ่านง่าย แต่วิธีการที่มีหลายร้อยบรรทัดซึ่งอ่านและกำหนดให้
self
ในหลาย ๆ ที่?
อะไรก็ได้ที่เกินกำลัง เพียงเพราะ OOP มีการใช้งานไม่ได้หมายความว่าจะไม่สามารถใช้ในทางที่ผิดหรือเขียนไม่ดี
- OOP หรือไม่วิธีการที่มีหลายร้อยบรรทัดเป็นปัญหาด้านคุณภาพของโค้ด
- ในขณะที่สถานะวัตถุสามารถจัดการได้และนั่นไม่ใช่ความคิดที่ไม่ดีโดยเนื้อแท้การมีวัตถุที่มีการเปลี่ยนแปลงสถานะอยู่ตลอดเวลาจนถึงจุดที่ไม่สามารถติดตามสถานะของมันได้อีกต่อไป แต่ก็เป็นปัญหาด้านคุณภาพของรหัสเช่นกัน
แต่สิ่งเหล่านี้ไม่ใช่ข้อโต้แย้งในการใช้ OOP เป็นกฎแบบครอบคลุม นั่นเหมือนกับการบอกว่าไม่ควรมีใครใช้ค้อนเพราะคุณเคยเห็นพ่อของคุณใช้ค้อนทุบหัวแม่มือ
ความผิดพลาดเกิดขึ้น แต่การมีอยู่ของความผิดพลาดไม่ได้หักล้างแนวคิดโดยรวม
เป็นการไม่ดีที่จะเรียกเวลาของวันว่า "ตอนนี้" ภายในวิธีการที่คำนวณบางอย่างเช่นสตริงเวลาของวันตามที่คุณได้แสดงไว้ นี้เป็นเพราะ,
หากคุณต้องการทราบเวลาของสตริงวันจากเวลาอื่นคุณไม่สามารถใช้วิธีนี้ได้ซึ่งทำให้วิธีนี้มีประโยชน์น้อยลงมากและคุณต้องทำตรรกะซ้ำเพื่อใช้ตรรกะนั้นในอีกทางหนึ่ง
หากคุณต้องการทราบเวลาของสตริงวัน แต่ต้องการเวลาจริงของวันในขณะนี้คุณจะสิ้นสุดการโทรเวลาของวันนี้สองครั้งและการเรียกสองครั้งที่แยกจากกันไปที่ "ตอนนี้" อาจเป็นค่าที่แตกต่างกันได้อย่างง่ายดายโดยที่ผู้เขียนโค้ด มักคาดหวังว่าพวกเขาจะตรงกันทุกประการ
ตามหลักการแล้วหากคุณต้องการเวลาของวัน "ตอนนี้" สิ่งนั้นจะได้รับเพียงครั้งเดียว (ต่ออะไรก็ตาม) และส่งผ่านเป็นพารามิเตอร์ไปยังรหัสใด ๆ ที่เกี่ยวข้องกับเวลา "ปัจจุบัน"