“ การหลีกเลี่ยงปัญหาโยโย่” เป็นเหตุผลที่ถูกต้องหรือไม่ที่จะยอมให้


42

ตามที่ครอบงำจิตใจดั้งเดิมคือเมื่อไม่ได้กลิ่นรหัส? ฉันควรสร้างวัตถุ ZipCode เพื่อแสดงรหัสไปรษณีย์แทนวัตถุ String

อย่างไรก็ตามจากประสบการณ์ของฉันฉันชอบดู

public class Address{
    public String zipCode;
}

แทน

public class Address{
    public ZipCode zipCode;
}

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

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

ดังนั้นฉันต้องการย้ายวิธีการ ZipCode เป็นคลาสใหม่ตัวอย่างเช่น:

เก่า:

public class ZipCode{
    public boolean validate(String zipCode){
    }
}

ใหม่:

public class ZipCodeHelper{
    public static boolean validate(String zipCode){
    }
}

ดังนั้นเฉพาะผู้ที่ต้องการตรวจสอบรหัสไปรษณีย์เท่านั้นจึงจะขึ้นอยู่กับคลาส ZipCodeHelper และฉันพบอีก "ประโยชน์" ในการรักษาความหลงไหลแบบดั้งเดิม: มันทำให้คลาสดูเหมือนรูปแบบต่อเนื่องของมันหากมีตัวอย่างเช่นตารางที่อยู่ที่มี zipCode คอลัมน์สตริง

คำถามของฉันคือ "การหลีกเลี่ยงปัญหา yo-yo" (ย้ายระหว่างคำจำกัดความของชั้นเรียน) เหตุผลที่ถูกต้องเพื่อให้ "ครอบงำจิตใจดั้งเดิม"?


9
@ jpmc26 คุณจะต้องตกใจเมื่อเห็นว่ารหัสไปรษณีย์ของเราซับซ้อนแค่ไหน - ไม่บอกว่ามันถูกต้อง แต่มันมีอยู่จริง
Jared Goguen

9
@ jpmc26 ฉันไม่สามารถดูว่าคุณได้รับจาก "ซับซ้อน" ถึง "ออกแบบไม่ดี" รหัสที่ซับซ้อนมักจะเป็นผลมาจากรหัสอย่างง่ายที่สัมผัสกับความซับซ้อนของโลกแห่งความจริงมากกว่าโลกอุดมคติที่เราอาจต้องการ "กลับไปที่ฟังก์ชั่นสองหน้าใช่ฉันรู้ว่ามันเป็นเพียงฟังก์ชั่นง่าย ๆ ในการแสดงหน้าต่าง แต่มันมีขนเล็ก ๆ น้อย ๆ และมีสิ่งต่าง ๆ ติดอยู่บนนั้นและไม่มีใครรู้ว่าทำไม การแก้ไข."
Kyralessa

18
@ jpmc26 - จุดของวัตถุห่อเช่น ZipCode คือความปลอดภัยของประเภท รหัสไปรษณีย์ไม่ใช่สตริง แต่เป็นรหัสไปรษณีย์ หากฟังก์ชั่นต้องการรหัสไปรษณีย์คุณควรจะสามารถส่งรหัสไปรษณีย์ได้เท่านั้นไม่ใช่สตริง
Davor Ždralo

4
สิ่งนี้ให้ความรู้สึกเฉพาะภาษาสูงภาษาต่าง ๆ ทำสิ่งต่าง ๆ ที่นี่ @ DavorŽdraloในช่วงเวลาเดียวกันเราควรเพิ่มประเภทตัวเลขจำนวนมาก "จำนวนเต็มบวกเท่านั้น", "เฉพาะเลขคู่" อาจเป็นประเภทได้
paul23

6
@ paul23 ใช่แล้วและเหตุผลหลักที่เราไม่มีนั่นก็คือภาษาจำนวนมากไม่สนับสนุนวิธีที่สวยงามในการนิยาม มันสมเหตุสมผลอย่างสมบูรณ์ในการกำหนด "อายุ" เป็นประเภทที่แตกต่างจาก "อุณหภูมิในหน่วยองศาเซลเซียส" หากเพียงเพื่อให้ตรวจพบ "userAge == currentTemperature" ว่าไร้สาระ
IMSoP

คำตอบ:


116

สมมติฐานคือคุณไม่จำเป็นต้องใช้ yo-yo กับคลาส ZipCode เพื่อทำความเข้าใจกับคลาส Address หาก ZipCode ได้รับการออกแบบมาอย่างดีควรเห็นได้ชัดว่าทำอะไรได้บ้างโดยการอ่านคลาสที่อยู่

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

ตัวอย่างเช่นฉันแน่ใจว่าคุณไม่ต้องการอ่านซอร์สโค้ดของ String ทุกครั้งที่คุณพบสตริงในโค้ด

การเปลี่ยนชื่อคลาสจาก ZipCode เป็น ZipCodeHelper แนะนำว่าตอนนี้มีสองแนวคิดแยกกัน: รหัสไปรษณีย์และตัวช่วยรหัสไปรษณีย์ ซับซ้อนเป็นสองเท่า และตอนนี้ระบบประเภทไม่สามารถช่วยคุณแยกแยะความแตกต่างระหว่างสตริงที่กำหนดเองและรหัสไปรษณีย์ที่ถูกต้องเนื่องจากมีประเภทเดียวกัน นี่คือที่ "ความหลงใหล" มีความเหมาะสม: คุณแนะนำทางเลือกที่ซับซ้อนและปลอดภัยน้อยลงเพียงเพราะคุณต้องการหลีกเลี่ยงกระดาษห่อหุ้มรอบ ๆ แบบดั้งเดิม

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

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


2
ฉันเห็นด้วยกับส่วน "หน่วยที่มีความหมาย" (หลัก -) แต่ไม่มากว่าการตรวจสอบรหัสไปรษณีย์และรหัสไปรษณีย์เป็นแนวคิดเดียวกัน ZipCodeHelper(ซึ่งฉันอยากจะโทรZipCodeValidator) อาจสร้างการเชื่อมต่อกับบริการบนเว็บได้เป็นอย่างดี นั่นจะไม่เป็นส่วนหนึ่งของความรับผิดชอบเดียว "เก็บข้อมูลรหัสไปรษณีย์" การทำให้ระบบประเภทไม่อนุญาตให้ใช้รหัสไปรษณีย์ที่ไม่ถูกต้องสามารถทำได้โดยการZipCodeสร้าง Constructor ของ package-private ของ Java และเรียกมันด้วยการZipCodeFactoryเรียกตัวตรวจสอบความถูกต้อง
R. Schmitz

16
@ R.Schmitz: นั่นไม่ใช่สิ่งที่ "รับผิดชอบ" ในความหมายของหลักการความรับผิดชอบเดียว แต่ในกรณีใด ๆ คุณควรใช้คลาสมากเท่าที่คุณต้องการตราบใดที่คุณใส่รหัสไปรษณีย์และการตรวจสอบความถูกต้อง OP ขอแนะนำผู้ช่วยแทนการห่อหุ้มรหัสไปรษณีย์ซึ่งเป็นความคิดที่ไม่ดี
JacquesB

1
ฉันต้องการที่จะไม่เห็นด้วยอย่างเคารพ SRP หมายถึงคลาสควรมี "หนึ่งและเหตุผลเดียวเท่านั้นที่จะเปลี่ยน" (เปลี่ยน "zipcode ประกอบด้วย" vs. "วิธีการตรวจสอบความถูกต้อง") กรณีเฉพาะที่นี่มีการอธิบายเพิ่มเติมในหนังสือClean Code : " วัตถุที่ซ่อนข้อมูลอยู่ข้างหลัง abstractions และแสดงฟังก์ชั่นที่ทำงานกับข้อมูลนั้นโครงสร้างข้อมูลเปิดเผยข้อมูลของพวกเขาและไม่มีฟังก์ชั่นที่มีความหมาย " - ZipCodeจะเป็น "โครงสร้างข้อมูล" และZipCodeHelper"วัตถุ" ในกรณีใด ๆ ฉันคิดว่าเราเห็นด้วยที่เราไม่ควรจะต้องผ่านการเชื่อมต่อเว็บไปยัง Constructor ZipCode
R. Schmitz

9
การใช้ดั้งเดิมนั้น IMHO เป็นธรรมในกรณีที่ไม่มีการตรวจสอบหรือตรรกะอื่น ๆ ขึ้นอยู่กับประเภทนี้โดยเฉพาะ => ฉันไม่เห็นด้วย แม้ว่าค่าทั้งหมดจะใช้ได้ แต่ฉันก็ยังคงชอบที่จะถ่ายทอดความหมายไปยังภาษามากกว่าที่จะใช้ภาษาดั้งเดิม หากฟังก์ชั่นสามารถเรียกใช้ในรูปแบบดั้งเดิมซึ่งไม่มีความหมายสำหรับการใช้ความหมายในปัจจุบันของมันแล้วมันไม่ควรจะเป็นประเภทดั้งเดิมมันควรจะเป็นประเภทที่เหมาะสมกับฟังก์ชั่นที่เหมาะสมเท่านั้นที่กำหนดไว้ (เป็นตัวอย่างการใช้intเป็น ID อนุญาตให้คูณ ID ด้วย ID ... )
Matthieu M.

@ R.Schmitz ฉันคิดว่ารหัสไปรษณีย์เป็นตัวอย่างที่ไม่ดีสำหรับความแตกต่างที่คุณทำ สิ่งที่เปลี่ยนแปลงบ่อยครั้งอาจเป็นตัวเลือกสำหรับการแยกFooและFooValidatorคลาส เราสามารถมีZipCodeคลาสที่ตรวจสอบความถูกต้องของรูปแบบและZipCodeValidatorบริการเว็บที่ได้รับความนิยมเพื่อตรวจสอบว่ารูปแบบที่ถูกต้องZipCodeเป็นปัจจุบันจริงหรือไม่ เรารู้ว่าการเปลี่ยนแปลงรหัสไปรษณีย์ แต่ในทางปฏิบัติเราจะมีรายการรหัสไปรษณีย์ที่ถูกต้องในZipCodeหรือในฐานข้อมูลท้องถิ่น
ไม่มี

55

ถ้าสามารถทำได้:

new ZipCode("totally invalid zip code");

และตัวสร้างสำหรับ ZipCode ไม่:

ZipCodeHelper.validate("totally invalid zip code");

จากนั้นคุณได้แยกไฟล์แคปซูเลชันและเพิ่มการพึ่งพาคลาส ZipCode หากนวกรรมิกไม่เรียกZipCodeHelper.validate(...)คุณจะต้องแยกตรรกะในเกาะของตัวเองโดยไม่บังคับใช้จริง คุณสามารถสร้างรหัสไปรษณีย์ที่ไม่ถูกต้อง

validateวิธีการที่ควรจะเป็นวิธีการที่คงอยู่ในระดับรหัสไปรษณีย์ ตอนนี้ความรู้เกี่ยวกับรหัสไปรษณีย์ที่ "ถูกต้อง" นั้นมาพร้อมกับคลาส ZipCode เนื่องจากตัวอย่างโค้ดของคุณดูเหมือน Java ตัวสร้างของ ZipCode ควรมีข้อยกเว้นหากมีการกำหนดรูปแบบที่ไม่ถูกต้อง:

public class ZipCode {
    private String zipCode;

    public ZipCode(string zipCode) {
        if (!validate(zipCode))
            throw new IllegalFormatException("Invalid zip code");

        this.zipCode = zipCode;
    }

    public static bool validate(String zipCode) {
        // logic to check format
    }

    @Override
    public String toString() {
        return zipCode;
    }
}

คอนสตรัคเตอร์ตรวจสอบรูปแบบและส่งข้อยกเว้นจึงป้องกันไม่ให้สร้างรหัสไปรษณีย์ที่ไม่ถูกต้องและvalidateวิธีการแบบคงที่มีให้รหัสอื่น ๆ เพื่อให้ตรรกะของการตรวจสอบรูปแบบถูกห่อหุ้มในคลาส ZipCode

ไม่มี "yo-yo" ในตัวแปรนี้ของคลาส ZipCode มันเรียกว่าการเขียนโปรแกรมเชิงวัตถุที่เหมาะสม


เราจะไม่สนใจความเป็นสากลที่นี่ซึ่งอาจทำให้คลาสอื่นเรียกว่า ZipCodeFormat หรือ PostalService (เช่น PostalService.isValidPostalCode (... ), PostalService.parsePostalCode (... ) ฯลฯ )


28
หมายเหตุ: ข้อได้เปรียบหลักด้วยวิธีการของ @Greg Burkhardt ที่นี่คือถ้ามีใครให้วัตถุ ZipCode แก่คุณคุณสามารถไว้วางใจได้ว่ามันมีสตริงที่ถูกต้องโดยไม่ต้องตรวจสอบอีกครั้งเนื่องจากประเภทและความจริงที่ว่ามันสร้างเสร็จเรียบร้อยแล้ว รับประกันได้ว่า หากคุณส่งสตริงไปรอบ ๆ คุณอาจรู้สึกว่าต้อง "ยืนยันการตรวจสอบ (zipCode)" ที่สถานที่ต่าง ๆ ในรหัสของคุณเพื่อให้แน่ใจว่าคุณมีรหัสไปรษณีย์ที่ถูกต้อง แต่ด้วยวัตถุ ZipCode ที่สร้างขึ้นเรียบร้อยแล้ว เนื้อหาถูกต้องโดยไม่ต้องตรวจสอบอีกครั้ง
ผู้ชายบางคน

3
@ R.Schmitz: ZipCode.validateวิธีการคือการตรวจสอบล่วงหน้าที่สามารถดำเนินการได้ก่อนที่จะเรียกตัวสร้างที่โยนข้อยกเว้น
เกร็ก Burghardt

10
@R.Schmitz: หากคุณมีข้อกังวลเกี่ยวกับข้อยกเว้นที่น่ารำคาญแนวทางอื่นในการก่อสร้างคือการสร้าง ZipCode Constructor ให้เป็นส่วนตัวและจัดให้มีฟังก์ชั่นโรงงานคงที่สาธารณะ (Zipcode.create?) ที่ดำเนินการตรวจสอบความถูกต้องของพารามิเตอร์ ส่งกลับค่า null ถ้าไม่สำเร็จและสร้างวัตถุ ZipCode และส่งคืนวัตถุนั้น ผู้โทรจะต้องตรวจสอบค่าที่ส่งคืนเป็นโมฆะเสมอ ในทางตรงกันข้ามถ้าคุณอยู่ในนิสัยเช่นการตรวจสอบเสมอ (regex ตรวจสอบหรือไม่ ฯลฯ ) ก่อนที่จะสร้าง ZipCode ข้อยกเว้นอาจไม่น่ารำคาญในทางปฏิบัติ
ผู้ชายบางคน

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

6
คุณไม่สามารถตรวจสอบ ZipCode แบบอิสระได้ดังนั้นอย่าทำเช่นนั้น คุณต้องใช้วัตถุประเทศเพื่อค้นหากฎการตรวจสอบความถูกต้องของ ZipCode / PostCode
โจชัว

11

หากคุณต่อสู้กับคำถามนี้มากบางทีภาษาที่คุณใช้ไม่ใช่เครื่องมือที่เหมาะสมสำหรับงาน "ดั้งเดิมที่พิมพ์โดเมน" ประเภทนี้ง่ายต่อการแสดงตัวอย่างเช่น F #

เช่นคุณสามารถเขียน:

type ZipCode = ZipCode of string
type Town = Town of string

type Adress = {
  zipCode: ZipCode
  town: Town
  //etc
}

let adress1 = {
  zipCode = ZipCode "90210"
  town = Town "Beverly Hills"
}

let faultyAdress = {
  zipCode = "12345"  // <-Compiler error
  town = adress1.zipCode // <- Compiler error
}

สิ่งนี้มีประโยชน์มากสำหรับการหลีกเลี่ยงข้อผิดพลาดทั่วไปเช่นการเปรียบเทียบ id ของเอนทิตีต่าง ๆ และเนื่องจากการพิมพ์ดั้งเดิมเหล่านี้มีน้ำหนักเบากว่า C # หรือ Java-class มากคุณจึงจะใช้มันได้จริง


ที่น่าสนใจ - ว่ามันจะมีลักษณะเช่นถ้าคุณต้องการที่จะบังคับใช้การตรวจสอบของZipCode?
Hulk

4
@Hulk คุณสามารถเขียน OO-style ใน F # และสร้างประเภทเป็นคลาส อย่างไรก็ตามฉันชอบสไตล์การทำงานประกาศประเภทด้วยประเภท ZipCode = ส่วนตัว ZipCode ของสตริงและเพิ่มโมดูล ZipCode ด้วยฟังก์ชั่นสร้าง มีตัวอย่างบางส่วนที่นี่: gist.github.com/swlaschin/54cfff886669ccab895a
Guran

@ Bent-Tranberg ขอบคุณสำหรับการแก้ไข คุณถูกต้องตัวย่อแบบง่ายไม่ได้ให้ความปลอดภัยประเภทเวลารวบรวม
Guran

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

ใช่. แหล่งต้นฉบับของฉันถูกต้องไม่เป็นไร แต่น่าเสียดายที่รวมถึงตัวอย่างที่ได้รับการสนับสนุนว่าไม่ถูกต้อง Doh! ควร hav เพิ่งเชื่อมโยงกับ Wlaschin แทนที่จะพิมพ์รหัสตัวเอง :) fsharpforfunandprofit.com/posts/…
Guran

6

คำตอบนั้นขึ้นอยู่กับสิ่งที่คุณต้องการทำกับรหัสไปรษณีย์ นี่คือความเป็นไปได้สองอย่าง:

(1) ที่อยู่ทั้งหมดรับประกันว่าจะอยู่ในประเทศเดียว ไม่มีข้อยกเว้นเลย (เช่นไม่มีลูกค้าต่างประเทศหรือไม่มีพนักงานที่มีที่อยู่ส่วนตัวในต่างประเทศในขณะที่ทำงานให้กับลูกค้าต่างประเทศ) ประเทศนี้มีรหัสไปรษณีย์และพวกเขาสามารถคาดหวังว่าจะไม่เกิดปัญหาร้ายแรง (เช่นไม่ต้องป้อนข้อมูลแบบฟรี) เช่น "ปัจจุบัน D4B 6N2 แต่การเปลี่ยนแปลงนี้จะเกิดขึ้นทุก 2 สัปดาห์") รหัสไปรษณีย์ไม่ได้ใช้เพื่อการระบุที่อยู่เท่านั้น แต่ยังใช้เพื่อตรวจสอบความถูกต้องของข้อมูลการชำระเงินหรือวัตถุประสงค์อื่น ๆ - ภายใต้สถานการณ์เหล่านี้คลาสรหัสไปรษณีย์เหมาะสมเป็นอย่างยิ่ง

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


3

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

ฉันไม่เห็นด้วยโดยเฉพาะตัวอย่างรหัสที่คุณโพสต์

แต่ฉันก็สงสัยเช่นกันว่าจริง ๆ แล้วตอบคำถามของคุณหรือไม่

พิจารณาสถานการณ์ต่อไปนี้ (ดึงมาจากโครงการจริงที่ฉันกำลังทำอยู่):

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

ในสถานการณ์นี้การทำอะไรมากไปกว่าการตรวจสอบความถูกต้องของการแทรกด้วยข้อ จำกัด SQL regex (หรือเทียบเท่า ORM ของมัน) น่าจะเกินความจริงของความหลากหลายของ YAGNI


6
ข้อ จำกัด SQL regex ของคุณอาจถูกมองว่าเป็นประเภทที่ผ่านการรับรอง - ภายในฐานข้อมูลของคุณรหัสไปรษณีย์จะไม่ถูกจัดเก็บเป็น "VarChar" แต่ "VarChar ถูก จำกัด โดยกฎนี้" ใน DBMS บางรายการคุณสามารถตั้งชื่อ + ข้อ จำกัด ประเภทนั้นให้เป็น "ประเภทโดเมน" ที่นำกลับมาใช้ใหม่ได้ง่ายและเรากลับมาอยู่ในตำแหน่งที่แนะนำในการให้ข้อมูลประเภทที่มีความหมาย ฉันเห็นด้วยกับคำตอบของคุณในหลักการ แต่อย่าคิดว่าตัวอย่างนั้นตรงกัน ตัวอย่างที่ดีกว่าคือถ้าข้อมูลของคุณคือ "ข้อมูลเซ็นเซอร์ดิบ" และประเภทที่มีความหมายมากที่สุดคือ "อาร์เรย์ไบต์" เพราะคุณไม่รู้ว่าข้อมูลหมายถึงอะไร
IMSoP

@IMSoP จุดที่น่าสนใจ ไม่แน่ใจว่าฉันเห็นด้วย: คุณสามารถตรวจสอบรหัสไปรษณีย์ของสตริงใน Java (หรือภาษาอื่น ๆ ) ด้วย regex แต่ยังคงจัดการกับมันเป็นสตริงแทนที่จะเป็นประเภทที่สมบูรณ์ยิ่งขึ้น ขึ้นอยู่กับตรรกะของโดเมนการจัดการเพิ่มเติมอาจจำเป็น (ตัวอย่างเช่นการทำให้แน่ใจว่ารหัสไปรษณีย์ตรงกับสถานะสิ่งที่ยาก / เป็นไปไม่ได้ที่จะตรวจสอบกับ regex)
Jared Smith

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

คุณสามารถสร้างแบบจำลองสิ่งเช่น a RegexValidatedString, มีสตริงตัวเองและ regex ใช้ในการตรวจสอบมัน แต่ถ้าทุกกรณีมี regex ที่ไม่ซ้ำกัน (ซึ่งเป็นไปได้ แต่ไม่น่าเป็นไปได้) สิ่งนี้ดูเหมือนจะโง่และสิ้นเปลืองหน่วยความจำ (และอาจเป็นเวลารวบรวม regex) ดังนั้นคุณอาจวาง regex ลงในตารางแยกต่างหากและทิ้งคีย์ค้นหาในแต่ละอินสแตนซ์เพื่อค้นหามัน (ซึ่งเลวร้ายยิ่งเนื่องจากทางอ้อม) หรือคุณหาวิธีเก็บไว้หนึ่งครั้งสำหรับการแชร์ค่าแต่ละประเภททั่วไปกฎนั้น - - เช่น. เขตข้อมูลคงที่ในประเภทโดเมนหรือวิธีการเทียบเท่าตามที่ IMSoP กล่าว
Miral

1

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

อย่างไรก็ตามถึงกระนั้นก็ยังไม่ได้ใช้งานที่ถูกต้อง (หรือมากกว่าการแก้ปัญหา) ครอบงำจิตใจดั้งเดิม; ซึ่งตามที่ฉันเข้าใจส่วนใหญ่เน้นสองสิ่ง:

  1. การใช้ primitives เป็นค่าอินพุต (หรือแม้แต่เอาต์พุต) ของเมธอดโดยเฉพาะอย่างยิ่งเมื่อจำเป็นต้องมีการรวบรวม primitives
  2. คลาสที่เพิ่มคุณสมบัติพิเศษเมื่อเวลาผ่านไปโดยไม่ต้องพิจารณาใหม่ว่าควรจัดกลุ่มสิ่งเหล่านี้เป็นคลาสย่อยของตนเองหรือไม่

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

นี่คือวิธีที่คุณรู้ว่าคุณไม่จำเป็นต้องแบ่งย่อยใด ๆ เพิ่มเติม: การทำลายมันลงไปอีกจะทำให้เสียสมาธิจากการตั้งใจทำงานของAddressชั้นเรียน ในทำนองเดียวกันคุณไม่จำเป็นต้องใช้Nameคลาสย่อยในPersonชั้นเรียนเว้นแต่Name(ไม่มีบุคคลที่แนบมา) เป็นแนวคิดที่มีความหมายในโดเมนของคุณ ซึ่งมัน (ปกติ) ไม่ใช่ ชื่อนี้ใช้สำหรับระบุตัวบุคคลโดยปกติแล้วพวกเขาจะไม่มีค่าด้วยตนเอง


1
@RikD: จากคำตอบ: "คุณไม่จำเป็นต้องใช้Nameคลาสย่อยในPersonชั้นเรียนเว้นแต่ชื่อ (ไม่มีคนแนบ) เป็นแนวคิดที่มีความหมายในโดเมนของคุณ " เมื่อคุณมีการตรวจสอบความถูกต้องแบบกำหนดเองสำหรับชื่อชื่อนั้นจะกลายเป็นแนวคิดที่มีความหมายในโดเมนของคุณ ซึ่งฉันกล่าวถึงอย่างชัดเจนว่าเป็นกรณีการใช้งานที่ถูกต้องสำหรับการใช้ชนิดย่อย ประการที่สองสำหรับการตรวจสอบความถูกต้องของรหัสไปรษณีย์คุณกำลังแนะนำสมมติฐานเพิ่มเติมเช่นรหัสไปรษณีย์ที่จำเป็นต้องใช้รูปแบบของประเทศนั้น ๆ คุณกำลังเจาะลึกหัวข้อที่กว้างกว่าเจตนาของคำถามของ OP
Flater

5
"ที่อยู่เป็นแนวคิดที่กำหนดอย่างชัดเจนพร้อมคุณสมบัติที่จำเป็นอย่างชัดเจน (ถนน, หมายเลข, ไปรษณีย์, เมือง, รัฐ, ประเทศ) " - นั่นเป็นเพียงความผิดปกติ สำหรับวิธีที่ดีในการจัดการกับสิ่งนี้ให้ดูที่แบบฟอร์มที่อยู่ของ Amazon
R. Schmitz

4
@ Flater ดีฉันจะไม่ตำหนิคุณที่ไม่ได้อ่านรายชื่อเต็ม ๆ ของ falsehoods เพราะมันค่อนข้างยาว แต่มันมีตัวอักษร "ที่อยู่จะมีถนน", "ที่อยู่ต้องมีทั้งเมืองและประเทศ", "ที่อยู่ จะมีรหัสไปรษณีย์ "ฯลฯ ซึ่งตรงกันข้ามกับสิ่งที่ประโยคที่ยกมากล่าว
R. Schmitz

8
@GregBurghardt "รหัสไปรษณีย์จะถือว่า United States Postal Service และคุณสามารถได้รับชื่อเมืองจากรหัสไปรษณีย์เมืองต่างๆสามารถมีรหัสไปรษณีย์ได้หลายแห่ง แต่รหัสไปรษณีย์แต่ละแห่งจะผูกติดกับ 1 เมืองเท่านั้น" สิ่งนี้ไม่ถูกต้องโดยทั่วไป ฉันมีรหัสไปรษณีย์ที่ใช้เป็นหลักสำหรับเมืองใกล้เคียง แต่ที่อยู่อาศัยของฉันไม่ได้อยู่ที่นั่น รหัสไปรษณีย์ไม่สอดคล้องกับขอบเขตของรัฐเสมอไป ตัวอย่างเช่นมี 42223 มณฑลจากทั้งเทนเนสซีและเคนตั๊กกี้
JimmyJames

2
ในออสเตรียมีหุบเขาซึ่งเข้าถึงได้จากเยอรมนีเท่านั้น ( en.wikipedia.org/wiki/Kleinwalsertal ) มีสนธิสัญญาพิเศษสำหรับภูมิภาคนี้ซึ่งรวมถึงที่อยู่ในพื้นที่นี้ซึ่งมีทั้งรหัสไปรษณีย์ออสเตรียและเยอรมัน ดังนั้นโดยทั่วไปคุณไม่สามารถสันนิษฐานได้ว่าที่อยู่มีรหัสไปรษณีย์ที่ถูกต้องเพียงหนึ่งเดียวเท่านั้น)
Hulk

1

จากบทความ:

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

ซอร์สโค้ดถูกอ่านบ่อยกว่าที่เขียน ดังนั้นปัญหาของ yo-yo ที่ต้องสลับไปมาระหว่างหลาย ๆ ไฟล์เป็นเรื่องที่น่ากังวล

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

อย่างไรก็ตาม - ใช่การหลีกเลี่ยงเลเยอร์สิ่งที่เป็นนามธรรมเป็นสิ่งสำคัญ!

abstractions ที่ไม่น่ารำคาญทั้งหมดในระดับหนึ่งมีการรั่วไหล - กฎของ Abstractions ที่รั่วไหล

ตัวอย่างเช่นฉันไม่เห็นด้วยกับสมมติฐานที่ทำไว้ในคำตอบของ mmmaaaว่า "คุณไม่จำเป็นต้อง yo-yo ถึง [(เยี่ยมชม)] คลาส ZipCode เพื่อทำความเข้าใจคลาสที่อยู่" ประสบการณ์ของฉันเป็นสิ่งที่คุณทำ - อย่างน้อยสองสามครั้งแรกที่คุณอ่านรหัส อย่างไรก็ตามในขณะที่คนอื่น ๆ ได้ตั้งข้อสังเกตมีอยู่ครั้งเมื่อZipCodeระดับที่มีความเหมาะสม

YAGNI (Ya Ain't Gonna Need It) เป็นรูปแบบที่ดีกว่าในการติดตามเพื่อหลีกเลี่ยงรหัส Lasagna (รหัสที่มีเลเยอร์มากเกินไป) - abstractions เช่นประเภทและชั้นเรียนมีไว้เพื่อช่วยเหลือโปรแกรมเมอร์และไม่ควรใช้นอกเสียจากว่าพวกเขาจะความช่วยเหลือ

โดยส่วนตัวฉันมุ่งมั่นที่จะ "บันทึกบรรทัดของรหัส" (และแน่นอนที่เกี่ยวข้องกับ "บันทึกไฟล์ / โมดูล / คลาส" ฯลฯ ) ฉันมั่นใจว่ามีบางคนที่จะนำไปใช้กับฉันฉายาของ "ดั้งเดิมหลง" - ฉันคิดว่ามันสำคัญกว่าที่จะมีรหัสซึ่งเป็นเรื่องง่ายที่จะให้เหตุผลเกี่ยวกับกว่ากังวลเกี่ยวกับฉลากรูปแบบและรูปแบบการต่อต้าน ทางเลือกที่ถูกต้องของเวลาที่จะสร้างฟังก์ชั่นโมดูล / ไฟล์ / คลาสหรือวางฟังก์ชั่นในตำแหน่งทั่วไปเป็นสถานการณ์มาก ฉันตั้งเป้าหมายสำหรับฟังก์ชั่น 3-100 บรรทัดไฟล์ 80-500 บรรทัดและ "1, 2, n" สำหรับรหัสไลบรารีที่นำมาใช้ซ้ำได้ ( SLOC - ไม่รวมความคิดเห็นหรือแผ่นสร้างสำเร็จรูป) โดยทั่วไปฉันต้องการ SLOC ขั้นต่ำเพิ่มเติมอย่างน้อย 1 รายการต่อบรรทัด สำเร็จรูป)

รูปแบบเชิงบวกมากที่สุดได้เกิดขึ้นจากนักพัฒนาทำตรงนั้นเมื่อพวกเขาต้องการพวกเขา การเรียนรู้วิธีการเขียนรหัสที่อ่านได้นั้นสำคัญกว่าการพยายามใช้รูปแบบที่ไม่มีปัญหาเดียวกันเพื่อแก้ไข นักพัฒนาที่ดีสามารถใช้รูปแบบของโรงงานโดยไม่เคยเห็นมาก่อนในกรณีที่ผิดปกติซึ่งเหมาะกับปัญหาของพวกเขา ฉันใช้รูปแบบจากโรงงานรูปแบบผู้สังเกตการณ์และอาจเป็นร้อย ๆ โดยไม่ทราบชื่อของพวกเขา (กล่าวคือมี "รูปแบบการกำหนดตัวแปร" หรือไม่) สำหรับการทดสอบที่สนุกสนาน - ดูว่ามีรูปแบบ GoF จำนวนเท่าใดในภาษา JS - ฉันหยุดนับหลังจากประมาณ 12-15 ปีในปี 2009 รูปแบบ Factory นั้นง่ายเหมือนการส่งคืนวัตถุจากคอนสตรัคเตอร์ JS เช่น - ไม่จำเป็นต้องใช้ WidgetFactory

ดังนั้น - ใช่ , บางครั้งก็ ZipCodeเป็นระดับที่ดี อย่างไรก็ตามไม่ปัญหาโยโย่ไม่เกี่ยวข้องอย่างเคร่งครัด


0

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

  1. การตั้งชื่อไม่ดี ZipCode ดูดี ZoneImprovementPlanCode จะต้องได้รับการตรวจสอบจากผู้คนส่วนใหญ่
  2. การมีเพศสัมพันธ์ที่ไม่เหมาะสม สมมติว่าคลาส ZipCode มีการค้นหารหัสพื้นที่ คุณอาจคิดว่ามันสมเหตุสมผลเพราะมันมีประโยชน์ แต่มันไม่เกี่ยวข้องกับ ZipCode จริงๆและการใส่มันเข้าไปในนั้นหมายความว่าตอนนี้ผู้คนไม่รู้ว่าจะไปหาอะไร

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


-1

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.