มีวิธีที่ง่ายกว่าในการทดสอบการตรวจสอบอาร์กิวเมนต์และการเริ่มต้นฟิลด์ในวัตถุที่ไม่เปลี่ยนรูปแบบหรือไม่?


20

โดเมนของฉันประกอบด้วยคลาสที่ไม่เปลี่ยนรูปแบบง่าย ๆ มากมายเช่นนี้

public class Person
{
    public string FullName { get; }
    public string NameAtBirth { get; }
    public string TaxId { get; }
    public PhoneNumber PhoneNumber { get; }
    public Address Address { get; }

    public Person(
        string fullName,
        string nameAtBirth,
        string taxId,
        PhoneNumber phoneNumber,
        Address address)
    {
        if (fullName == null)
            throw new ArgumentNullException(nameof(fullName));
        if (nameAtBirth == null)
            throw new ArgumentNullException(nameof(nameAtBirth));
        if (taxId == null)
            throw new ArgumentNullException(nameof(taxId));
        if (phoneNumber == null)
            throw new ArgumentNullException(nameof(phoneNumber));
        if (address == null)
            throw new ArgumentNullException(nameof(address));

        FullName = fullName;
        NameAtBirth = nameAtBirth;
        TaxId = taxId;
        PhoneNumber = phoneNumber;
        Address = address;
    }
}

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

วิธีแก้ปัญหาที่แท้จริงคือ C # เพื่อรองรับประเภทอ้างอิงที่ไม่สามารถเปลี่ยนแปลงได้และไม่เป็นโมฆะ แต่ฉันจะทำอย่างไรเพื่อปรับปรุงสถานการณ์ในขณะเดียวกัน? มันคุ้มค่าที่จะเขียนแบบทดสอบทั้งหมดเหรอ? จะเป็นการดีหรือไม่ที่จะเขียนตัวสร้างรหัสสำหรับคลาสดังกล่าวเพื่อหลีกเลี่ยงการเขียนการทดสอบสำหรับแต่ละรายการ?


นี่คือสิ่งที่ฉันมีตอนนี้ตามคำตอบ

ฉันสามารถทำให้การตรวจสอบว่างและการเริ่มต้นคุณสมบัติง่ายขึ้นเพื่อให้มีลักษณะเช่นนี้:

FullName = fullName.ThrowIfNull(nameof(fullName));
NameAtBirth = nameAtBirth.ThrowIfNull(nameof(nameAtBirth));
TaxId = taxId.ThrowIfNull(nameof(taxId));
PhoneNumber = phoneNumber.ThrowIfNull(nameof(phoneNumber));
Address = address.ThrowIfNull(nameof(address));

ใช้การดำเนินการต่อไปนี้โดยRobert Harvey :

public static class ArgumentValidationExtensions
{
    public static T ThrowIfNull<T>(this T o, string paramName) where T : class
    {
        if (o == null)
            throw new ArgumentNullException(paramName);

        return o;
    }
}

การทดสอบการตรวจสอบค่าว่างนั้นทำได้ง่ายโดยใช้GuardClauseAssertionจากAutoFixture.Idioms(ขอขอบคุณสำหรับข้อเสนอแนะEsben Skov Pedersen ):

var fixture = new Fixture().Customize(new AutoMoqCustomization());
var assertion = new GuardClauseAssertion(fixture);
assertion.Verify(typeof(Address).GetConstructors());

สิ่งนี้สามารถบีบอัดได้ยิ่งขึ้น:

typeof(Address).ShouldNotAcceptNullConstructorArguments();

ใช้วิธีการขยายนี้:

public static void ShouldNotAcceptNullConstructorArguments(this Type type)
{
    var fixture = new Fixture().Customize(new AutoMoqCustomization());
    var assertion = new GuardClauseAssertion(fixture);

    assertion.Verify(type.GetConstructors());
}

3
หัวเรื่อง / แท็กพูดคุยเกี่ยวกับการทดสอบ (หน่วย) ของค่าฟิลด์ที่บ่งบอกถึงการทดสอบหน่วยหลังการก่อสร้างของวัตถุ แต่ข้อมูลโค้ดแสดงการตรวจสอบอาร์กิวเมนต์อินพุต / การตรวจสอบพารามิเตอร์ สิ่งเหล่านี้ไม่ใช่แนวคิดเดียวกัน
Erik Eidt

2
ในปีที่แล้วคุณสามารถเขียนเทมเพลต T4 ที่จะทำให้สำเร็จรูปประเภทนี้ง่ายขึ้น (คุณอาจพิจารณาสตริง isEmpty () เกิน == null)
Erik Eidt

1
คุณลองมาดูที่สำนวน autofixture? nuget.org/packages/AutoFixture.Idioms
Esben Skov Pedersen

1
ที่เกี่ยวข้อง: stackoverflow.com/questions/291340/…
Doc Brown

1
คุณสามารถใช้Fody / NullGuardได้แม้ว่าจะดูเหมือนว่ามันไม่มีโหมดอนุญาตให้ใช้งานเริ่มต้น
บ๊อบ

คำตอบ:


5

ฉันสร้างเทมเพลต t4 อย่างแน่นอนสำหรับกรณีประเภทนี้ เพื่อหลีกเลี่ยงการเขียนจำนวนมากสำเร็จรูปสำหรับชั้นเรียนที่ไม่เปลี่ยนรูป

https://github.com/xaviergonz/T4Immutable T4Immutable เป็นเทมเพลต T4 สำหรับแอป C # .NET ที่สร้างรหัสสำหรับคลาสที่ไม่เปลี่ยนรูป

โดยเฉพาะการพูดคุยเกี่ยวกับการทดสอบที่ไม่เป็นโมฆะถ้าคุณใช้สิ่งนี้:

[PreNotNullCheck, PostNotNullCheck]
public string FirstName { get; }

ตัวสร้างจะเป็นดังนี้:

public Person(string firstName) {
  // pre not null check
  if (firstName == null) throw new ArgumentNullException(nameof(firstName));

  // assignations + PostConstructor() if needed

  // post not null check
  if (this.FirstName == null) throw new NullReferenceException(nameof(this.FirstName));
}

ต้องบอกว่าถ้าคุณใช้คำอธิบายประกอบ JetBrains สำหรับการตรวจสอบ null คุณสามารถทำสิ่งนี้ได้:

[JetBrains.Annotations.NotNull, ConstructorParamNotNull]
public string FirstName { get; }

และตัวสร้างจะเป็นดังนี้:

public Person([JetBrains.Annotations.NotNull] string firstName) {
  // pre not null check is implied by ConstructorParamNotNull
  if (firstName == null) throw new ArgumentNullException(nameof(firstName));

  FirstName = firstName;
  // + PostConstructor() if needed

  // post not null check implied by JetBrains.Annotations.NotNull on the property
  if (this.FirstName == null) throw new NullReferenceException(nameof(this.FirstName));
}

นอกจากนี้ยังมีคุณสมบัติเพิ่มเติมอีกเล็กน้อยกว่าคุณสมบัตินี้


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

16

คุณสามารถปรับปรุงให้ดีขึ้นได้ด้วยการปรับโครงสร้างอย่างง่ายที่สามารถลดปัญหาในการเขียนรั้วเหล่านั้นทั้งหมด ก่อนอื่นคุณต้องใช้วิธีการขยายนี้:

internal static T ThrowIfNull<T>(this T o, string paramName) where T : class
{
    if (o == null)
        throw new ArgumentNullException(paramName);

    return o;
}

จากนั้นคุณสามารถเขียน:

public class Person
{
    public string FullName { get; }
    public string NameAtBirth { get; }
    public string TaxId { get; }
    public PhoneNumber PhoneNumber { get; }
    public Address Address { get; }

    public Person(
        string fullName,
        string nameAtBirth,
        string taxId,
        PhoneNumber phoneNumber,
        Address address)
    {
        FullName = fullName.ThrowIfNull(nameof(fullName));
        NameAtBirth = nameAtBirth.ThrowIfNull(nameof(nameAtBirth));
        TaxId = taxId.ThrowIfNull(nameof(taxId));
        PhoneNumber = phoneNumber.ThrowIfNull(nameof(fullName));
        Address = address.ThrowIfNull(nameof(address));
    }
}

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

เทคนิคอื่น ๆ ที่มีความหรูหรามากขึ้นในแนวคิด แต่มีความก้าวหน้าที่ซับซ้อนมากขึ้นในการดำเนินการเช่นการตกแต่งพารามิเตอร์ที่มี[NotNull]คุณลักษณะและการใช้การสะท้อนเช่นนี้

ที่กล่าวว่าคุณอาจไม่จำเป็นต้องทำการทดสอบเหล่านี้ทั้งหมดยกเว้นว่าคลาสของคุณเป็นส่วนหนึ่งของ API สาธารณะ


3
แปลกที่นี่ถูกลงคะแนน มันคล้ายกับวิธีที่ได้จากคำตอบ upvoted ที่สุดยกเว้นว่าจะไม่ขึ้นอยู่กับ Roslyn
Robert Harvey

สิ่งที่ฉันไม่ชอบเกี่ยวกับเรื่องนี้คือมันเสี่ยงต่อการดัดแปลงตัวสร้าง
jpmc26

@ jpmc26: นั่นหมายความว่าอย่างไร
Robert Harvey

1
เมื่อฉันอ่านคำตอบของคุณมันแนะนำว่าคุณไม่ได้ทดสอบตัวสร้างแต่คุณสร้างวิธีการทั่วไปที่เรียกว่าในแต่ละพารามิเตอร์และทดสอบที่ หากคุณเพิ่มคู่ของคุณสมบัติ / อาร์กิวเมนต์ใหม่และลืมทดสอบnullบนอาร์กิวเมนต์ของตัวสร้างคุณจะไม่ได้รับการทดสอบที่ล้มเหลว
jpmc26

@ jpmc26: โดยธรรมชาติ แต่นั่นก็เป็นจริงเช่นกันถ้าคุณเขียนแบบธรรมดาดังที่แสดงใน OP วิธีการทั่วไปทั้งหมดคือการย้ายthrowด้านนอกของตัวสร้าง; คุณไม่ได้ถ่ายโอนการควบคุมภายในตัวสร้างที่อื่น มันเป็นเพียงวิธีการใช้งานและวิธีการทำสิ่งต่าง ๆอย่างคล่องแคล่วหากคุณเลือก
Robert Harvey

7

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

public class Person
{
    public string FullName { get; }
    public string NameAtBirth { get; }
    public string TaxId { get; }
    public PhoneNumber PhoneNumber { get; }
    public Address Address { get; }

    public Person(
        string fullName,
        string nameAtBirth,
        string taxId,
        PhoneNumber phoneNumber,
        Address address)
    {
        FullName = fullName ?? throw new ArgumentNullException(nameof(fullName));
        NameAtBirth = nameAtBirth ?? throw new ArgumentNullException(nameof(nameAtBirth));
        TaxId = taxId ?? throw new ArgumentNullException(nameof(taxId)); ;
        PhoneNumber = phoneNumber ?? throw new ArgumentNullException(nameof(phoneNumber)); ;
        Address = address ?? throw new ArgumentNullException(nameof(address)); ;
    }
}

คุณสามารถทดลองโยนแสดงออกผ่านลองโรสลิน webapp


กะทัดรัดมากขึ้นขอบคุณ ครึ่งหนึ่งเป็นหลายบรรทัด รอคอยที่จะ C # 7!
Botond Balázs

@ BotondBalázsคุณสามารถลองใช้งานได้ในหน้าตัวอย่างของ VS15 ตอนนี้ถ้าคุณต้องการ
user1306322

7

ฉันประหลาดใจที่ไม่มีใครพูดถึงNullGuard.Fodyเลย สามารถใช้งานได้ผ่านทางNuGetและจะทำการตรวจสอบโมฆะเหล่านั้นให้เป็นอัตโนมัติในระหว่างการรวบรวม

ดังนั้นรหัสคอนสตรัคเตอร์ของคุณก็คือ

public Person(
    string fullName,
    string nameAtBirth,
    string taxId,
    PhoneNumber phoneNumber,
    Address address)
{
    FullName = fullName;
    NameAtBirth = nameAtBirth;
    TaxId = taxId;
    PhoneNumber = phoneNumber;
    Address = address;
}

และ NullGuard จะเพิ่มการตรวจสอบโมฆะเหล่านั้นเพื่อให้คุณแปลงเป็นสิ่งที่คุณเขียน

หมายเหตุแม้ว่าที่ NullGuard คือเลือกออกคือว่ามันจะเพิ่มการตรวจสอบ null เหล่านั้นไปยังทุกวิธีการและข้อโต้แย้งคอนสตรัค, getter ทรัพย์สินและหมาและแม้กระทั่งการตรวจสอบค่าผลตอบแทนวิธีเว้นแต่ว่าคุณได้ช่วยให้ค่า null กับ[AllowNull]แอตทริบิวต์


ขอบคุณดูเหมือนว่าเป็นวิธีแก้ปัญหาทั่วไปที่ดีมาก แม้ว่าจะเป็นเรื่องที่โชคร้ายมาก แต่ก็ปรับเปลี่ยนพฤติกรรมของภาษาตามค่าเริ่มต้น ฉันต้องการมันมากขึ้นถ้ามันทำงานโดยการเพิ่ม[NotNull]คุณลักษณะแทนการไม่อนุญาตให้เป็นโมฆะเป็นค่าเริ่มต้น
Botond Balázs

@ BotondBalázs: PostSharpมีคุณลักษณะนี้พร้อมกับแอตทริบิวต์การตรวจสอบความถูกต้องของพารามิเตอร์อื่น ๆ ซึ่งแตกต่างจาก Fody มันไม่ฟรีแม้ว่า นอกจากนี้รหัสที่สร้างขึ้นจะเรียกเข้าไปใน PostSharp DLLs ดังนั้นคุณต้องรวมรหัสเหล่านั้นกับผลิตภัณฑ์ของคุณ Fody จะเรียกใช้โค้ดในระหว่างการคอมไพล์และไม่จำเป็นต้องใช้ขณะรันไทม์
Roman Reiner

@ BotondBalázs: ฉันก็พบว่ามันแปลก ๆ ในตอนแรกที่ NullGuard ใช้ต่อการผิดนัด แต่มันสมเหตุสมผลจริงๆ ในฐานรหัสใด ๆ ที่เหมาะสมช่วยให้ null ควรจะมากน้อยกว่าการตรวจสอบ null หากคุณจริงๆต้องการให้ที่ไหนสักแห่งโมฆะเลือกออกกองกำลังวิธีการที่คุณได้เป็นอย่างมากกับมัน
Roman Reiner

5
ใช่ว่าเป็นที่เหมาะสม แต่มันละเมิดหลักการน้อยมหัศจรรย์ หากโปรแกรมเมอร์คนใหม่ไม่รู้ว่าโครงการใช้ NullGuard พวกเขากำลังแปลกใจอย่างมาก! โดยวิธีการที่ฉันไม่เข้าใจ downvote อย่างนี้เป็นคำตอบที่มีประโยชน์มาก
Botond Balázs

1
@ BotondBalázs / Roman ดูเหมือนว่า NullGuard มีโหมดใหม่ที่ชัดเจนซึ่งเปลี่ยนวิธีการทำงานตามค่าเริ่มต้น
Kyle W

5

มันคุ้มค่าที่จะเขียนแบบทดสอบทั้งหมดเหรอ?

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

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

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

อาจจะ? สิ่งนั้นสามารถทำได้อย่างง่ายดายด้วยการสะท้อนกลับ สิ่งที่ต้องพิจารณาคือการสร้างรหัสสำหรับรหัสจริงดังนั้นคุณไม่มีคลาส N ที่อาจมีข้อผิดพลาดของมนุษย์ การป้องกันบั๊ก> การตรวจจับบั๊ก


ขอขอบคุณ. บางทีฉันอาจจะไม่ชัดเจนเพียงพอในคำถามของฉัน แต่ฉันต้องการเขียนตัวกำเนิดที่สร้างคลาสที่ไม่เปลี่ยนรูปไม่ได้ทดสอบสำหรับพวกเขา
Botond Balázs

2
ผมเคยเห็นยังมีหลายครั้งแทนที่จะเป็นif...throwพวกเขาจะมีThrowHelper.ArgumentNull(myArg, "myArg")ที่ทางตรรกะยังคงมีและคุณไม่ได้รับ dinged ในความคุ้มครองรหัส ในกรณีที่วิธีการที่จะทำArgumentNull if...throw
Matthew

2

มันคุ้มค่าที่จะเขียนแบบทดสอบทั้งหมดเหรอ?

ไม่
เพราะฉันค่อนข้างมั่นใจว่าคุณได้ทดสอบคุณสมบัติเหล่านั้นผ่านการทดสอบทางตรรกะอื่น ๆ ที่ใช้คลาสเหล่านั้น

ตัวอย่างเช่นคุณสามารถทดสอบคลาส Factory ที่มีการยืนยันตามคุณสมบัติเหล่านั้น (ตัวอย่างอินสแตนซ์ที่สร้างขึ้นด้วยNameคุณสมบัติที่กำหนดอย่างเหมาะสม)

หากคลาสเหล่านั้นสัมผัสกับ API สาธารณะที่ผู้ใช้ส่วนที่สาม / ผู้ใช้ปลายทาง (@EJoshua ขอบคุณที่สังเกตเห็น) การทดสอบที่คาดว่าArgumentNullExceptionจะมีประโยชน์

ในขณะที่รอ C # 7 คุณสามารถใช้วิธีการขยาย

public MyClass(string name)
{
    name.ThrowArgumentNullExceptionIfNull(nameof(name));
}

public static void ThrowArgumentNullExceptionIfNull(this object value, string paramName)
{
    if(value == null)
        throw new ArgumentNullException(paramName);
}

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


1
นั่นเป็นข้อโต้แย้งที่น่าเชื่อถือสำหรับการไม่ทดสอบการเริ่มต้นคุณสมบัติ
Botond Balázs

ขึ้นอยู่กับว่าคุณใช้รหัสอย่างไร หากรหัสเป็นส่วนหนึ่งของห้องสมุดสาธารณะคุณไม่ควรวางใจคนอื่นในการรู้วิธีการใช้ห้องสมุดของคุณ (โดยเฉพาะถ้าพวกเขาไม่สามารถเข้าถึงซอร์สโค้ดได้) อย่างไรก็ตามหากเป็นสิ่งที่คุณใช้ภายในเพื่อให้โค้ดของคุณทำงานคุณควรรู้วิธีใช้โค้ดของคุณเองดังนั้นการตรวจสอบจึงมีวัตถุประสงค์น้อยกว่า
EJoshuaS - Reinstate Monica

2

คุณสามารถเขียนวิธีดังต่อไปนี้ได้เสมอ:

// Just to illustrate how to call this
private static void SomeMethod(string a, string b, string c, string d)
    {
        ValidateArguments(a, b, c, d);
        // ...
    }

    // This is the one to use as a utility function
    private static void ValidateArguments(params object[] args)
    {
        for (int i = 0; i < args.Length; i++)
        {
            if (args[i] == null)
            {
                StackTrace trace = new StackTrace();
                // Get the method that called us
                MethodBase info = trace.GetFrame(1).GetMethod();

                // Get information on the parameter that is null so we can add its name to the exception
                ParameterInfo param = info.GetParameters()[i];

                // Raise the exception on behalf of the caller
                throw new ArgumentNullException(param.Name);
            }
        }
    }

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

คุณยังสามารถขยายสิ่งนี้เพื่อทำการตรวจสอบเฉพาะประเภทอื่น ๆ ตัวอย่างเช่นถ้าคุณมีกฎที่สตริงไม่สามารถเป็นช่องว่างหรือหมดจดอย่างหมดจดคุณสามารถเพิ่มเงื่อนไขต่อไปนี้:

// Note that we already know based on the previous condition that args[i] is not null
else if (args[i].GetType() == typeof(string))
            {
                string argCast = arg as string;

                if (!argCast.Trim().Any())
                {
                    ParameterInfo param = GetParameterInfo(i);

                    throw new ArgumentException(param.Name + " is empty or consists only of whitespace");
                }
            }

GetParameterInfo วิธีฉันอ้างถึงโดยทั่วไปจะสะท้อน (เพื่อให้ฉันไม่ต้องพิมพ์สิ่งเดียวกันซ้ำไปซ้ำมาซึ่งจะเป็นการละเมิดหลักการ DRY ):

private static ParameterInfo GetParameterInfo(int index)
    {
        StackTrace trace = new StackTrace();

        // Note that we have to go 2 methods back to get the ValidateArguments method's caller
        MethodBase info = trace.GetFrame(2).GetMethod();

        // Get information on the parameter that is null so we can add its name to the exception
        ParameterInfo param = info.GetParameters()[index];

        return param;
    }

1
ทำไมสิ่งนี้ถึงถูกลดระดับลง? มันไม่เลวร้ายเกินไป
Gaspa79

0

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

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

ฉันขอแนะนำให้ใช้ API การตรวจสอบความถูกต้องของ Java และส่งออกกระบวนการตรวจสอบ ฉันขอแนะนำให้มีความรับผิดชอบสี่ (คลาส) ที่เกี่ยวข้อง:

  1. อ็อบเจ็กต์ข้อเสนอแนะที่มีการตรวจสอบความถูกต้อง Java ที่ทำหมายเหตุประกอบด้วยพารามิเตอร์ด้วยวิธีการตรวจสอบความถูกต้องที่ส่งกลับชุดของ ConstraintViolations ข้อดีอย่างหนึ่งคือคุณสามารถผ่านวัตถุนี้โดยไม่ต้องยืนยันว่าถูกต้อง คุณสามารถชะลอการตรวจสอบความถูกต้องจนกว่าจะมีความจำเป็นโดยไม่ต้องลองอินสแตนซ์วัตถุโดเมน วัตถุการตรวจสอบสามารถใช้ในชั้นที่แตกต่างกันเพราะมันเป็น POJO ที่ไม่มีความรู้เฉพาะชั้น มันอาจเป็นส่วนหนึ่งของ API สาธารณะของคุณ

  2. โรงงานสำหรับวัตถุโดเมนที่รับผิดชอบในการสร้างวัตถุที่ถูกต้อง คุณส่งผ่านวัตถุข้อเสนอแนะและโรงงานจะเรียกการตรวจสอบและสร้างวัตถุโดเมนหากทุกอย่างเรียบร้อย

  3. วัตถุโดเมนของตัวเองซึ่งไม่สามารถเข้าถึงได้สำหรับการสร้างอินสแตนซ์สำหรับนักพัฒนาอื่น ๆ เป็นส่วนใหญ่ สำหรับวัตถุประสงค์ในการทดสอบฉันแนะนำให้มีคลาสนี้ในขอบเขตของแพ็คเกจ

  4. ส่วนต่อประสานโดเมนสาธารณะเพื่อซ่อนวัตถุโดเมนที่เป็นรูปธรรมและทำให้เข้าใจผิดอย่างหนัก

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

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


1
ฉันไม่เห็นด้วยกับจุดสุดท้ายของคุณอย่างเคารพ การไม่ต้องพิมพ์หม้อไอน้ำเดียวกันเป็นครั้งที่ 100 จะช่วยลดโอกาสในการทำผิดพลาด ลองจินตนาการว่านี่เป็น Java ไม่ใช่ C # ฉันจะต้องกำหนดเขตข้อมูลสำรองและวิธีการทะเยอทะยานสำหรับแต่ละคุณสมบัติ รหัสจะไม่สามารถอ่านได้อย่างสมบูรณ์และสิ่งที่สำคัญจะถูกบดบังโดยโครงสร้างพื้นฐาน clunky
Botond Balázs
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.