จะแก้ไขคุณสมบัติของวัตถุได้อย่างไร?

Aug 18 2020

ผมเคยอ่านเมื่อเร็ว ๆ นี้เป็นบทความที่ดีเกี่ยวกับการทดสอบหน่วย มีตัวอย่างของวิธีการที่ไม่ดีซึ่งออกแบบมาไม่ดี หน้าตาเป็นแบบนี้

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";
}

มีบางสิ่งที่ผู้เขียนชี้ว่าเป็นรูปแบบการต่อต้าน:

  1. มันเชื่อมโยงอย่างแน่นหนากับแหล่งข้อมูลที่เป็นรูปธรรม (อ่านวันที่และเวลาปัจจุบันจากเครื่องที่ทำงาน)
  2. เป็นการละเมิดหลักการความรับผิดชอบเดียว (SRP)
  3. มันอยู่เกี่ยวกับข้อมูลที่จำเป็นในการทำงานให้ลุล่วง นักพัฒนาต้องอ่านซอร์สโค้ดจริงทุกบรรทัดเพื่อทำความเข้าใจว่าอินพุตที่ซ่อนอยู่ถูกใช้และมาจากที่ใด ลายเซ็นของวิธีการเพียงอย่างเดียวไม่เพียงพอที่จะเข้าใจพฤติกรรมของวิธีการ

ฉันเขียนโค้ดใน 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)
  1. accelerate มีอินพุตที่ซ่อนอยู่: self.power
  2. ลายเซ็นของวิธีการเพียงอย่างเดียวไม่เพียงพอที่จะเข้าใจพฤติกรรมของวิธีการ มีเอาต์พุตที่ซ่อนอยู่ (?)self.speed

เป็นวิธีการเล็ก ๆ และอ่านง่าย แต่วิธีการที่มีหลายร้อยบรรทัดซึ่งอ่านและกำหนดให้selfในหลาย ๆ ที่? หากผู้ที่ไม่ได้ตั้งชื่ออย่างถูกต้องนักพัฒนาจะมีปัญหาใหญ่ที่จะเข้าใจในสิ่งที่มันไม่และแม้ว่าผู้ที่มีชื่อถูกต้องนักพัฒนาควรอ่านทั้งการดำเนินการที่จะรู้ว่าถ้ามันไม่ปรับเปลี่ยนบางสิ่งหรือถ้าบริบทเพิ่มเติมจะถูกฉีดด้วยselfself

ในทางกลับกันเมื่อฉันพยายามโค้ดทุกวิธีโดยไม่ใช้selfด้วยอินพุต (อาร์กิวเมนต์) และเอาต์พุต (ค่าส่งคืน) ฉันจะส่งตัวแปรหนึ่งตัวผ่านหลายวิธีและฉันจะทำซ้ำเอง

แล้วจะจัดการselfอย่างไรและใช้อย่างไรให้เหมาะสม? จะทำให้ชัดเจนได้อย่างไรว่าวิธีใดที่ใช้เป็นอินพุตและสิ่งที่ปรับเปลี่ยน (เอาต์พุต)

คำตอบ

6 amon Aug 18 2020 at 18:16

ดีที่สุดคืออย่าให้มากเกินไป ใช่เป็นความจริงที่ว่าฟังก์ชันบริสุทธิ์ขนาดเล็กที่ไม่มีการไหลของข้อมูลที่ชัดเจนนั้นง่ายต่อการทดสอบมากกว่าการกลายพันธุ์ซึ่งส่งผลให้เกิดการกระทำบางอย่างในระยะ แต่ด้วยเหตุผลความไม่แน่นอนความไม่สมบูรณ์และการพึ่งพาไม่ได้เป็นปัญหา พวกเขาทำให้บางสิ่งสะดวกขึ้นมาก

ตามกฎทั่วไป: ยิ่งโค้ดบางตัวเข้าใกล้ตรรกะทางธุรกิจของซอฟต์แวร์บางตัวมากเท่าไหร่โค้ดก็จะยิ่งบริสุทธิ์ไม่เปลี่ยนรูปใช้งานได้ชัดเจนและทดสอบได้ ยิ่งโค้ดบางส่วนอยู่ใกล้กับเลเยอร์ด้านนอกของแอปพลิเคชันมากเท่าไหร่ฟังก์ชันการทำงานที่น้อยลงนั่นก็คุ้มค่ากับการทดสอบหน่วยอย่างรอบคอบและการออกแบบที่ทดสอบได้น้อยลงก็ไม่เป็นไร ตัวอย่างเช่นโค้ดที่ปิด 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_xsome_operation()

สิ่งที่ไม่ใช่ปัญหาคือการsome_operation()ใช้ฟิลด์ของวัตถุที่ถูกเรียกใช้ นั่นก็เหมือนกับ ... ประเด็นทั้งหมด ตราบเท่าที่ข้อมูลในวัตถุนี้มีขนาดเล็กพอสมควรและสามารถจัดการได้การดำเนินการดังกล่าวก็ใช้ได้ หากคุณต้องการจินตนาการคุณสามารถเรียกสิ่งนี้ว่าอินสแตนซ์ของ "หลักการแยกส่วนต่อประสาน" ปัญหาส่วนใหญ่เกิดจากวัตถุเทพเจ้าที่เทอะทะจริง ๆ ซึ่งมีหลายสิบช่อง

คำถามไม่ควรเป็นว่าผู้เรียกภายนอกของเมธอดรู้ว่าจะใช้ฟิลด์ใดของวัตถุหรือไม่ ผู้เรียกไม่ควรต้องรู้เรื่องนี้วัตถุควรเป็นสิ่งที่ห่อหุ้มไว้ คำถามที่สำคัญกว่าคือการอ้างอิงและความสัมพันธ์เหล่านี้ชัดเจนจากภายในออบเจ็กต์หรือไม่ การมีหลายช่องแสดงถึงโอกาสมากมายที่สิ่งต่างๆจะไม่ตรงกัน

4 FilipMilovanović Aug 18 2020 at 22:24

ประการแรกเป็นที่น่าสังเกตว่าตัวอย่างในบทความค่อนข้างมีการสร้างขึ้น (ด้วยเหตุผลในทางปฏิบัติ) และบริบทนั้นมีความสำคัญเมื่อพูดถึงสิ่งเหล่านี้ เช่นหากคุณกำลังเขียนเครื่องมือขนาดเล็กแบบใช้ครั้งเดียวมีเหตุผลเพียงเล็กน้อยที่จะรบกวนการออกแบบมากเกินไป แต่สมมติว่านี่เป็นส่วนหนึ่งของโครงการระยะยาวและคุณสามารถคาดหวังได้อย่างสมเหตุสมผลว่าโค้ดนี้จะได้รับประโยชน์จากการเปลี่ยนแปลงการออกแบบบางอย่าง (หรือคุณต้องใช้การเปลี่ยนแปลงที่ขัดแย้งกับการออกแบบปัจจุบันอยู่แล้ว) และมาตรวจสอบกัน ในบริบทนั้น

นี่คือรหัสสำหรับการอ้างอิง:

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บางครั้งการฉีดคอนสตรัคเตอร์ไม่สามารถทำได้ (เช่นเฟรมเวิร์กอาจต้องใช้คอนสตรัคเตอร์แบบไม่มีพารามิเตอร์) ดังนั้นการพึ่งพาจึงถูกฉีดผ่านวิธีการหรือคุณสมบัติแทน แต่ก็ไม่เหมาะอย่างยิ่ง

3 GregBurghardt Aug 18 2020 at 18:06

โดยปกติคำถามประเภทนี้สามารถตอบได้โดยดูที่รหัสโดยใช้วิธีการ

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วิธีที่คำนวณเวลาเร่งความเร็วที่เหมาะสมตามกำลังของรถในปัจจุบัน

ลายเซ็นของวิธีการเพียงอย่างเดียวไม่เพียงพอที่จะเข้าใจพฤติกรรมของวิธีการ

ดูเหมือนว่าฉันจะพบ รถสามารถเร่งความเร็วได้ หลังจากเร่งความเร็วได้มากกว่าเดิม นั่นคือทั้งหมดที่ฉันต้องรู้

1 J.G. Aug 18 2020 at 18:07

วิธีการพื้นฐานคือการใส่ตรรกะให้มากที่สุดเท่าที่จะเป็นไปได้ในฟังก์ชันที่อยู่นอกคลาส (หรือเป็นแบบคงที่) จากนั้นเรียกมันอย่างรัดกุมในวิธีการที่ขึ้นอยู่กับสถานะ (การเรียกเหล่านี้ยังคงจำเป็นต้องซ่อนคุณสมบัติที่ส่งผ่านในทางเทคนิคจากลายเซ็นของพวกเขา แต่นั่นเป็นจุดสำคัญของ 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เนื่องจากไม่ได้อยู่ในคลาสดังนั้นจึงไม่มีพารามิเตอร์ที่ซ่อนอยู่ที่รบกวนคุณ (ในฐานะที่เป็นแบบฝึกหัดคุณอาจเขียนวิธีการนี้ขึ้นใหม่เพื่อใช้=แทน+=ซึ่งไม่เกี่ยวข้องกับประเด็นที่ทำไว้ที่นี่)

1 Flater Aug 19 2020 at 10:11

ข้อสังเกตของคุณมีความถูกต้อง (ถ้าไม่ใช่ส่วนใหญ่) แต่ข้อสรุปที่คุณได้รับจากข้อสรุปนั้นมากเกินไป

  1. มันเชื่อมโยงอย่างแน่นหนากับแหล่งข้อมูลที่เป็นรูปธรรม (อ่านวันที่และเวลาปัจจุบันจากเครื่องที่ทำงาน)

แก้ไข. ควรส่งค่าวันที่เป็นพารามิเตอร์หรือควรใส่ค่าอ้างอิงแบบนาฬิกา

โปรดทราบว่าการฉีดแบบพึ่งพาต้องใช้คลาสและวิธีการที่ไม่คงที่ เพิ่มเติมในภายหลัง

รับทราบคำแนะนำหลัง (การฉีดการพึ่งพา) คำถามของคุณโต้แย้งกับแนวคิดนี้และนั่นคือสิ่งที่การสังเกตของคุณหลุดออกไปจากราง เพิ่มเติมในภายหลัง

  1. เป็นการละเมิดหลักการความรับผิดชอบเดียว (SRP)

ฉันไม่เห็นว่ามันเป็นอย่างไรและคุณไม่ได้เหตุผลว่าทำไมคุณถึงคิดว่ามันเป็นเช่นนั้น วิธีนี้ทำได้อย่างหนึ่ง SRP ไม่ได้เน้นว่าจะมีการฉีดการอ้างอิงหรือไม่ SRP มุ่งเน้นไปที่ตรรกะที่มีอยู่ภายในคลาส คลาสนี้มีจุดประสงค์ที่กำหนดไว้อย่างเคร่งครัดคือสร้างป้ายกำกับที่เป็นมิตรกับมนุษย์สำหรับช่วงเวลาปัจจุบันของวัน

เพื่อความชัดเจน: โค้ดสามารถปรับปรุงได้ แต่ SRP ไม่ใช่สิ่งที่ควรคำนึงถึงว่าเป็นการละเมิดที่นี่

อาร์กิวเมนต์ที่เรียกค่าวันที่และเวลาเป็นความรับผิดชอบที่ไม่ต่อเนื่องเป็นอาร์กิวเมนต์ที่ยากลำบาก ความรับผิดชอบใด ๆสามารถแบ่งย่อยออกเป็นความรับผิดชอบที่เล็กกว่าได้ - แต่มีเส้นคั่นระหว่างสิ่งที่สมเหตุสมผลและสิ่งที่เกินความสามารถ สมมติว่าวิธีนี้บ่งบอกว่าเวลาปัจจุบันของวันกำลังได้รับการประเมินนี่ไม่ใช่การละเมิด SRP

  1. มันอยู่เกี่ยวกับข้อมูลที่จำเป็นในการทำงานให้ลุล่วง นักพัฒนาต้องอ่านซอร์สโค้ดจริงทุกบรรทัดเพื่อทำความเข้าใจว่าอินพุตที่ซ่อนอยู่ใช้อะไร ...

ที่เถียงได้ เมื่อฉันเห็น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 เป็นกฎแบบครอบคลุม นั่นเหมือนกับการบอกว่าไม่ควรมีใครใช้ค้อนเพราะคุณเคยเห็นพ่อของคุณใช้ค้อนทุบหัวแม่มือ
ความผิดพลาดเกิดขึ้น แต่การมีอยู่ของความผิดพลาดไม่ได้หักล้างแนวคิดโดยรวม

ErikEidt Aug 19 2020 at 11:38

เป็นการไม่ดีที่จะเรียกเวลาของวันว่า "ตอนนี้" ภายในวิธีการที่คำนวณบางอย่างเช่นสตริงเวลาของวันตามที่คุณได้แสดงไว้ นี้เป็นเพราะ,

  • หากคุณต้องการทราบเวลาของสตริงวันจากเวลาอื่นคุณไม่สามารถใช้วิธีนี้ได้ซึ่งทำให้วิธีนี้มีประโยชน์น้อยลงมากและคุณต้องทำตรรกะซ้ำเพื่อใช้ตรรกะนั้นในอีกทางหนึ่ง

  • หากคุณต้องการทราบเวลาของสตริงวัน แต่ต้องการเวลาจริงของวันในขณะนี้คุณจะสิ้นสุดการโทรเวลาของวันนี้สองครั้งและการเรียกสองครั้งที่แยกจากกันไปที่ "ตอนนี้" อาจเป็นค่าที่แตกต่างกันได้อย่างง่ายดายโดยที่ผู้เขียนโค้ด มักคาดหวังว่าพวกเขาจะตรงกันทุกประการ

ตามหลักการแล้วหากคุณต้องการเวลาของวัน "ตอนนี้" สิ่งนั้นจะได้รับเพียงครั้งเดียว (ต่ออะไรก็ตาม) และส่งผ่านเป็นพารามิเตอร์ไปยังรหัสใด ๆ ที่เกี่ยวข้องกับเวลา "ปัจจุบัน"